Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve CRS Caching, add CoordinateReferenceSystem.equals overload #33

Merged
merged 3 commits into from
Jul 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>1.8</source>
<target>1.8</target>
<debug>true</debug>
<encoding>UTF-8</encoding>
</configuration>
Expand Down
35 changes: 33 additions & 2 deletions src/main/java/org/locationtech/proj4j/CRSFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import org.locationtech.proj4j.io.Proj4FileReader;
import org.locationtech.proj4j.parser.Proj4Parser;

import java.io.IOException;

/**
* A factory which can create {@link CoordinateReferenceSystem}s
* from a variety of ways
Expand All @@ -39,7 +41,6 @@ public class CRSFactory {

// TODO: add method to allow reading from arbitrary PROJ4 CS file


/**
* Gets the {@link Registry} used by this factory.
*
Expand Down Expand Up @@ -126,9 +127,39 @@ public CoordinateReferenceSystem createFromParameters(String name, String[] para
return parser.parse(name, params);
}

/**
* Finds a EPSG Code
* from a PROJ.4 projection parameter string.
* <p>
* An example of a valid PROJ.4 projection parameter string is:
* <pre>
* +proj=aea +lat_1=50 +lat_2=58.5 +lat_0=45 +lon_0=-126 +x_0=1000000 +y_0=0 +ellps=GRS80 +units=m
* </pre>
*
* @param paramStr a PROJ.4 projection parameter string
* @return the specified {@link CoordinateReferenceSystem}
* @throws IOException if there was an issue in reading EPSG file
*/
public String readEpsgFromParameters(String paramStr) throws IOException {
return readEpsgFromParameters(splitParameters(paramStr));
}

/**
* Finds a EPSG Code
* defined by an array of PROJ.4 projection parameters.
* PROJ.4 parameters are generally of the form
* "<tt>+name=value</tt>".
*
* @param params an array of PROJ.4 projection parameters
* @return s String EPSG code
* @throws IOException if there was an issue in reading EPSG file
*/
public String readEpsgFromParameters(String[] params) throws IOException {
return csReader.readEpsgCodeFromFile(params);
}

private static String[] splitParameters(String paramStr) {
String[] params = paramStr.split("\\s+");
return params;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.locationtech.proj4j.units.Unit;
import org.locationtech.proj4j.units.Units;

import java.util.Arrays;

/**
* Represents a projected or geodetic geospatial coordinate system,
* to which coordinates may be referenced.
Expand Down Expand Up @@ -119,4 +121,16 @@ public CoordinateReferenceSystem createGeographic() {
public String toString() {
return name;
}

@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that instanceof CoordinateReferenceSystem) {
CoordinateReferenceSystem cr = (CoordinateReferenceSystem) that;
return name.equals(cr.name) && datum.isEqual(cr.getDatum()) && Arrays.equals(params, cr.params);
}
return false;
}
}
110 changes: 71 additions & 39 deletions src/main/java/org/locationtech/proj4j/io/Proj4FileReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
*/
package org.locationtech.proj4j.io;

import org.locationtech.proj4j.util.Pair;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Proj4FileReader {
Expand Down Expand Up @@ -71,49 +74,14 @@ private StreamTokenizer createTokenizer(BufferedReader reader) {
return t;
}

private String[] readFile(BufferedReader reader, String name)
throws IOException {
private String[] readFile(BufferedReader reader, String name) throws IOException {
StreamTokenizer t = createTokenizer(reader);

t.nextToken();
while (t.ttype == '<') {
t.nextToken();
if (t.ttype != StreamTokenizer.TT_WORD)
throw new IOException(t.lineno() + ": Word expected after '<'");
String crsName = t.sval;
t.nextToken();
if (t.ttype != '>')
throw new IOException(t.lineno() + ": '>' expected");
t.nextToken();
List v = new ArrayList();

while (t.ttype != '<') {
if (t.ttype == '+')
t.nextToken();
if (t.ttype != StreamTokenizer.TT_WORD)
throw new IOException(t.lineno() + ": Word expected after '+'");
String key = t.sval;
t.nextToken();


// parse =arg, if any
if (t.ttype == '=') {
t.nextToken();
//Removed check to allow for proj4 hack +nadgrids=@null
//if ( t.ttype != StreamTokenizer.TT_WORD )
// throw new IOException( t.lineno()+": Value expected after '='" );
String value = t.sval;
t.nextToken();
addParam(v, key, value);
} else {
// add param with no value
addParam(v, key, null);
}
}
t.nextToken();
if (t.ttype != '>')
throw new IOException(t.lineno() + ": '<>' expected");
t.nextToken();
Pair<String, List> pair = parseTokenizer(t);
String crsName = pair.fst();
List v = pair.snd();

// found requested CRS?
if (crsName.equals(name)) {
Expand Down Expand Up @@ -156,4 +124,68 @@ public String[] getParameters(String crsName) {
return null;
}

public String readEpsgCodeFromFile(String[] params) throws IOException {
InputStream inStr = Proj4FileReader.class.getClassLoader().getResourceAsStream("proj4/nad/epsg");

if (inStr == null) {
throw new IllegalStateException("Unable to access CRS file: EPSG");
}
BufferedReader reader = new BufferedReader(new InputStreamReader(inStr));

StreamTokenizer t = createTokenizer(reader);

t.nextToken();
while (t.ttype == '<') {
Pair<String, List> pair = parseTokenizer(t);
String crsName = pair.fst();
List v = pair.snd();

String[] paramsParsed = (String[]) v.toArray(new String[0]);

if(Arrays.equals(params, paramsParsed)) return crsName;
}
return null;
}

private static Pair<String, List> parseTokenizer(StreamTokenizer t) throws IOException {
t.nextToken();
if (t.ttype != StreamTokenizer.TT_WORD)
throw new IOException(t.lineno() + ": Word expected after '<'");
String crsName = t.sval;
t.nextToken();
if (t.ttype != '>')
throw new IOException(t.lineno() + ": '>' expected");
t.nextToken();
List v = new ArrayList();

while (t.ttype != '<') {
if (t.ttype == '+')
t.nextToken();
if (t.ttype != StreamTokenizer.TT_WORD)
throw new IOException(t.lineno() + ": Word expected after '+'");
String key = t.sval;
t.nextToken();


// parse =arg, if any
if (t.ttype == '=') {
t.nextToken();
//Removed check to allow for proj4 hack +nadgrids=@null
//if ( t.ttype != StreamTokenizer.TT_WORD )
// throw new IOException( t.lineno()+": Value expected after '='" );
String value = t.sval;
t.nextToken();
addParam(v, key, value);
} else {
// add param with no value
addParam(v, key, null);
}
}
t.nextToken();
if (t.ttype != '>')
throw new IOException(t.lineno() + ": '<>' expected");
t.nextToken();

return Pair.create(crsName, v);
}
}
63 changes: 48 additions & 15 deletions src/main/java/org/locationtech/proj4j/util/CRSCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,64 @@
*/
package org.locationtech.proj4j.util;

import java.util.HashMap;
import java.util.Map;

import org.locationtech.proj4j.*;

public class CRSCache {

private static Map<String, CoordinateReferenceSystem> projCache = new HashMap<String, CoordinateReferenceSystem>();
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

public class CRSCache {
private static CRSFactory crsFactory = new CRSFactory();

// TODO: provide limit on number of items in cache (LRU)
private ConcurrentHashMap<String, CoordinateReferenceSystem> crsCache = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, String> epsgCache = new ConcurrentHashMap<>();

public CRSCache() {
super();
public CRSCache CRSCache() {
crsCache = new ConcurrentHashMap<>();
epsgCache = new ConcurrentHashMap<>();
return this;
}

public CRSCache CRSCache(ConcurrentHashMap<String, CoordinateReferenceSystem> crsCache, ConcurrentHashMap<String, String> epsgCache) {
this.crsCache = crsCache;
this.epsgCache = epsgCache;
return this;
}

public CoordinateReferenceSystem createFromName(String name)
throws UnsupportedParameterException, InvalidValueException, UnknownAuthorityCodeException {
CoordinateReferenceSystem proj = (CoordinateReferenceSystem) projCache.get(name);
if (proj == null) {
proj = crsFactory.createFromName(name);
projCache.put(name, proj);
}
return proj;
CoordinateReferenceSystem res = crsCache.get(name);
if(res != null) return res;
return crsCache.computeIfAbsent(name, k -> crsFactory.createFromName(name));
}

public CoordinateReferenceSystem createFromParameters(String name, String paramStr)
throws UnsupportedParameterException, InvalidValueException {
String nonNullName = name == null ? "" : name;
String key = nonNullName + paramStr;
CoordinateReferenceSystem res = crsCache.get(key);
if(res != null) return res;
return crsCache.computeIfAbsent(key, k -> crsFactory.createFromParameters(name, paramStr));
}

public CoordinateReferenceSystem createFromParameters(String name, String[] params)
throws UnsupportedParameterException, InvalidValueException {
String nonNullName = name == null ? "" : name;
String key = nonNullName + String.join(" ", params);
CoordinateReferenceSystem res = crsCache.get(key);
if(res != null) return res;
return crsCache.computeIfAbsent(key, k -> crsFactory.createFromParameters(name, params));
}

public String readEpsgFromParameters(String paramStr) {
String res = epsgCache.get(paramStr);
if(res != null) return res;
return epsgCache.computeIfAbsent(paramStr, k -> { try { return crsFactory.readEpsgFromParameters(paramStr); } catch (IOException e) { return null; } });
}

public String readEpsgFromParameters(String[] params) {
String paramStr = String.join(" ", params);
String res = epsgCache.get(paramStr);
if(res != null) return res;
return epsgCache.computeIfAbsent(paramStr, k -> { try { return crsFactory.readEpsgFromParameters(params); } catch (IOException e) { return null; } });
}
}
78 changes: 78 additions & 0 deletions src/main/java/org/locationtech/proj4j/util/Pair.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright 2009, 2017 Martin Davis
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.locationtech.proj4j.util;

public class Pair<A, B> {

private A first;
private B second;

public Pair() {
super();
}

public Pair(A first, B second) {
this.first = first;
this.second = second;
}

public A fst() {
return first;
}

public void setFirst(A first) {
this.first = first;
}

public B snd() {
return second;
}

public void setSecond(B second) {
this.second = second;
}

@Override
public String toString() {
return "<" + first + "," + second + ">";
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Pair<?, ?> other = (Pair<?, ?>) obj;
if (first == null) {
if (other.first != null)
return false;
} else if (!first.equals(other.first))
return false;
if (second == null) {
if (other.second != null)
return false;
} else if (!second.equals(other.second))
return false;
return true;
}

public static <A, B> Pair<A, B> create(A first, B second) {
return new Pair<A, B>(first, second);
}
}