Commit 10d11156 authored by Matija Obreza's avatar Matija Obreza

Merge branch '5-include-distance-from-country' into 'master'

Resolve "Include distance from country"

Closes #5

See merge request !2
parents f5469f9a f693ca5b
Pipeline #5204 passed with stage
in 1 minute and 14 seconds
...@@ -33,4 +33,15 @@ public interface CountryOfOriginService { ...@@ -33,4 +33,15 @@ public interface CountryOfOriginService {
*/ */
String getCountries(float longitude, float latitude, String origCty, int allowedDistanceMargin) throws Exception; String getCountries(float longitude, float latitude, String origCty, int allowedDistanceMargin) throws Exception;
/**
* Get distance from georeference to closest point on border of specified country
*
* @param longitude the longitude
* @param latitude the latitude
* @param origCty the country ISO code
* @return distance to closest point on country border or -1 if not found
* @throws Exception
*/
double distanceToBorder(float longitude, float latitude, String origCty) throws Exception;
} }
...@@ -22,6 +22,7 @@ import java.net.MalformedURLException; ...@@ -22,6 +22,7 @@ import java.net.MalformedURLException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch; import org.apache.commons.lang3.time.StopWatch;
import org.genesys.geotools.service.CountryOfOriginService; import org.genesys.geotools.service.CountryOfOriginService;
import org.genesys.geotools.service.LonLatCacheKey; import org.genesys.geotools.service.LonLatCacheKey;
...@@ -31,8 +32,10 @@ import org.geotools.data.FeatureSource; ...@@ -31,8 +32,10 @@ import org.geotools.data.FeatureSource;
import org.geotools.factory.CommonFactoryFinder; import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator; import org.geotools.feature.FeatureIterator;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.JTSFactoryFinder; import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.GeodeticCalculator;
import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.simple.SimpleFeatureType;
...@@ -47,7 +50,9 @@ import com.google.common.cache.CacheStats; ...@@ -47,7 +50,9 @@ import com.google.common.cache.CacheStats;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.operation.distance.DistanceOp;
/** /**
* Polygons used here are derived from OSM data, © OpenStreetMap contributors. * Polygons used here are derived from OSM data, © OpenStreetMap contributors.
...@@ -72,6 +77,9 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService { ...@@ -72,6 +77,9 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService {
/** The country cache. */ /** The country cache. */
private LoadingCache<LonLatCacheKey, String> countryCache; private LoadingCache<LonLatCacheKey, String> countryCache;
/** The country cache. */
private LoadingCache<LonLatCacheKey, Double> countryDistanceCache;
/** The debug. */ /** The debug. */
private final boolean debug = false; private final boolean debug = false;
...@@ -80,7 +88,7 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService { ...@@ -80,7 +88,7 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService {
public void setDataFolderPath(String dataFolderPath) { public void setDataFolderPath(String dataFolderPath) {
this.dataFolderPath = dataFolderPath; this.dataFolderPath = dataFolderPath;
} }
static { static {
try { try {
// Initialize stuff // Initialize stuff
...@@ -106,14 +114,21 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService { ...@@ -106,14 +114,21 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService {
sourceAdmin0 = dataStoreAdm0.getFeatureSource(dataStoreAdm0.getTypeNames()[0]); sourceAdmin0 = dataStoreAdm0.getFeatureSource(dataStoreAdm0.getTypeNames()[0]);
sourceAdmin0X = dataStoreAdm0X.getFeatureSource(dataStoreAdm0X.getTypeNames()[0]); sourceAdmin0X = dataStoreAdm0X.getFeatureSource(dataStoreAdm0X.getTypeNames()[0]);
countryCache = CacheBuilder.newBuilder().maximumSize(5000).recordStats().expireAfterWrite(20, TimeUnit.SECONDS).build( countryCache = CacheBuilder.newBuilder().maximumSize(5000).recordStats().expireAfterWrite(20, TimeUnit.SECONDS).build(new CacheLoader<LonLatCacheKey, String>() {
new CacheLoader<LonLatCacheKey, String>() { @Override
@Override public String load(final LonLatCacheKey key) throws Exception {
public String load(final LonLatCacheKey key) throws Exception { // LOG.debug("Loading");
// LOG.debug("Loading"); return _getCountry(key.getLongitude(), key.getLatitude(), key.getOrigCty(), key.getAllowedDistanceMargin());
return _getCountry(key.getLongitude(), key.getLatitude(), key.getOrigCty(), key.getAllowedDistanceMargin()); }
} });
});
countryDistanceCache = CacheBuilder.newBuilder().maximumSize(5000).recordStats().expireAfterWrite(20, TimeUnit.SECONDS).build(new CacheLoader<LonLatCacheKey, Double>() {
@Override
public Double load(final LonLatCacheKey key) throws Exception {
// LOG.debug("Loading");
return _distanceToBorder(key.getLongitude(), key.getLatitude(), key.getOrigCty());
}
});
} }
/* /*
...@@ -121,8 +136,7 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService { ...@@ -121,8 +136,7 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService {
* @see org.genesys.geotools.LandOrSeaService#isOnLand(float, float, int) * @see org.genesys.geotools.LandOrSeaService#isOnLand(float, float, int)
*/ */
@Override @Override
public String getCountries(final float longitude, final float latitude, final String origCty, final int allowedDistanceMargin) public String getCountries(final float longitude, final float latitude, final String origCty, final int allowedDistanceMargin) throws Exception {
throws Exception {
// 1 geographical mile is 1855.3248 metres for WGS84 // 1 geographical mile is 1855.3248 metres for WGS84
// 1855.3248m * 60 = 111319.488m // 1855.3248m * 60 = 111319.488m
...@@ -149,8 +163,7 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService { ...@@ -149,8 +163,7 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService {
* @return the string * @return the string
* @throws Exception the exception * @throws Exception the exception
*/ */
private String _getCountry(final float longitude, final float latitude, final String origCtyISO, final int allowedDistanceMargin) private String _getCountry(final float longitude, final float latitude, final String origCtyISO, final int allowedDistanceMargin) throws Exception {
throws Exception {
// LOG.debug(longitude + ", " + latitude + " " + origCtyISO); // LOG.debug(longitude + ", " + latitude + " " + origCtyISO);
final StopWatch stopWatch = new StopWatch(); final StopWatch stopWatch = new StopWatch();
...@@ -161,8 +174,7 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService { ...@@ -161,8 +174,7 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService {
// sourceAdmin0.getSchema().getGeometryDescriptor() // sourceAdmin0.getSchema().getGeometryDescriptor()
// .getCoordinateReferenceSystem(); // .getCoordinateReferenceSystem();
final ReferencedEnvelope bbox = new ReferencedEnvelope(longitude - 30, longitude + 30, latitude - 30, latitude + 30, final ReferencedEnvelope bbox = new ReferencedEnvelope(longitude - 30, longitude + 30, latitude - 30, latitude + 30, DefaultGeographicCRS.WGS84);
DefaultGeographicCRS.WGS84);
final Filter filterExact = final Filter filterExact =
// ff.and( // ff.and(
...@@ -193,7 +205,7 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService { ...@@ -193,7 +205,7 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService {
LOG.debug("Processing time split: " + processingTime); LOG.debug("Processing time split: " + processingTime);
} }
if ((sb.length() == 0) || !sb.toString().contains(origCtyISO)) { if ((sb.length() == 0) || StringUtils.trimToNull(origCtyISO) == null || !sb.toString().contains(origCtyISO)) {
// if (sb.length() > 0) sb.append(", "); // if (sb.length() > 0) sb.append(", ");
// sb.append("???"); // sb.append("???");
// Filter filterBuffered = // Filter filterBuffered =
...@@ -233,14 +245,63 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService { ...@@ -233,14 +245,63 @@ public class CountryOfOriginServiceImpl implements CountryOfOriginService {
return sb.toString(); return sb.toString();
} }
@Override
public double distanceToBorder(float longitude, float latitude, String origCty) throws Exception {
try {
return countryDistanceCache.get(new LonLatCacheKey(longitude, latitude, origCty, 0));
} catch (final ExecutionException e) {
throw new Exception(e.getCause());
}
}
private double _distanceToBorder(float longitude, float latitude, String origCty) throws Exception {
Coordinate coordinate = new Coordinate(longitude, latitude);
final Point point = geometryFactory.createPoint(coordinate);
final Filter filterExact = ff.equal(ff.property("ISO3"), ff.literal(origCty), false);
LOG.trace("Distance of {} to {}", coordinate, origCty);
try {
final FeatureCollection<SimpleFeatureType, SimpleFeature> matchingFeatures = sourceAdmin0.getFeatures(filterExact);
if (matchingFeatures.size() > 0) {
if (LOG.isTraceEnabled()) {
try (FeatureIterator<SimpleFeature> features = matchingFeatures.features()) {
while (features.hasNext()) {
final SimpleFeature feature = features.next();
LOG.trace("{}: {} Attrs={}", feature.getID(), feature.getDefaultGeometryProperty().getValue(), feature.getAttributes());
}
}
}
try (FeatureIterator<SimpleFeature> features = matchingFeatures.features()) {
if (features.hasNext()) {
final SimpleFeature feature = features.next();
MultiPolygon geometry = (MultiPolygon) feature.getDefaultGeometry();
GeodeticCalculator gc = new GeodeticCalculator(DefaultGeographicCRS.WGS84);
gc.setStartingPosition(JTS.toDirectPosition(DistanceOp.nearestPoints(geometry, point)[0], DefaultGeographicCRS.WGS84));
gc.setDestinationPosition(JTS.toDirectPosition(coordinate, DefaultGeographicCRS.WGS84));
return gc.getOrthodromicDistance();
} else {
return -1.0d;
}
}
} else {
LOG.debug("No geometry with ISO={}", origCty);
return -1.0d;
}
} catch (IOException e) {
throw e;
}
}
/** /**
* Prints the cache. * Prints the cache.
*/ */
public void printCache() { public void printCache() {
final CacheStats stats = countryCache.stats(); final CacheStats stats = countryCache.stats();
if (LOG.isInfoEnabled()) { if (LOG.isInfoEnabled()) {
LOG.info("Hit count=" + stats.hitCount() + " rate=" + stats.hitRate() + " Miss count=" + stats.missCount() + " rate=" + stats LOG.info("Hit count=" + stats.hitCount() + " rate=" + stats.hitRate() + " Miss count=" + stats.missCount() + " rate=" + stats.missRate());
.missRate());
} }
} }
} }
/*
* Copyright 2016 Global Crop Diversity Trust
*
* 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.genesys.geotools.cli;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.genesys.geotools.service.impl.CountryOfOriginServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CountryDistance {
private final static Logger LOG = LoggerFactory.getLogger(CountryDistance.class);
public static void main(final String arg[]) throws Exception {
if (arg.length == 0) {
final CountryOfOriginServiceImpl countryOfOriginService = new CountryOfOriginServiceImpl();
countryOfOriginService.afterPropertiesSet();
final BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
final Pattern pattern = Pattern.compile("^(\\w{3})[\\s,]+(\\-?\\d*\\.?\\d*)[\\s,]+(\\-?\\d*\\.?\\d*)$");
LOG.info("Expects input rows in format: ORIGCTY\tDECLONGITUDE\tDECLATITUDE");
LOG.info("Enter 'q' to quit.");
System.out.println("ORIGCTY\tDECLONGITUDE\tDECLATITUDE\tRESULT");
String input = null;
do {
input = br.readLine();
if ((input == null) || "q".equals(input))
break;
final Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
final String origCty = matcher.group(1).trim();
final float longitude = Float.parseFloat(matcher.group(2).replace(",", "."));
final float latitude = Float.parseFloat(matcher.group(3).replace(",", "."));
System.out.println(origCty + "\t" + longitude + "\t" + latitude + "\t" + countryOfOriginService.distanceToBorder(longitude, latitude, origCty));
} else {
LOG.info("Invalid format: " + input);
}
} while ((input != null) && !"q".equals(input));
} else {
throw new RuntimeException("Nono.");
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment