package com.opendoorlogistics.core.geometry.operations; import gnu.trove.map.hash.TObjectByteHashMap; import com.opendoorlogistics.core.cache.ApplicationCache; import com.opendoorlogistics.core.cache.RecentlyUsedCache; import com.opendoorlogistics.core.geometry.Spatial; import com.opendoorlogistics.core.utils.ObjectDefaultSystemHashingDecorator; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; public class GeomContains { private static int MAX_CACHE_POINT_SIZE_PER_RESULTS_OBJ = 100000; private static class CacheKey{ final Geometry geometry; final ObjectDefaultSystemHashingDecorator geomHashingDecorator; final String espg; CacheKey(Geometry g, String espg) { this.geomHashingDecorator = new ObjectDefaultSystemHashingDecorator(g); this.geometry = g; this.espg = espg; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((espg == null) ? 0 : espg.hashCode()); result = prime * result + ((geomHashingDecorator == null) ? 0 : geomHashingDecorator.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; CacheKey other = (CacheKey) obj; if (espg == null) { if (other.espg != null) return false; } else if (!espg.equals(other.espg)) return false; if (geomHashingDecorator == null) { if (other.geomHashingDecorator != null) return false; } else if (!geomHashingDecorator.equals(other.geomHashingDecorator)) return false; return true; } } private static class CachedResultRecord{ Geometry projected; TObjectByteHashMap<Coordinate> results = new TObjectByteHashMap<Coordinate>(); } /** * Calculate if the input geometry contains the input point, using the projection of both * @param g * @param c * @return */ public static boolean containsPoint(Geometry g, Coordinate c){ // Get results object (for whole geometry) from cache if it exists CacheKey key = new CacheKey(g, null); RecentlyUsedCache cache = ApplicationCache.singleton().get(ApplicationCache.PROJECTED_GEOMETRY_CONTAINS_CACHE); Object cached = cache.get(key); CachedResultRecord result = (CachedResultRecord)cached; if(result==null){ result = new CachedResultRecord(); } // Check if we already have the result if(result.results.containsKey(c)){ return result.results.get(c)==1; } // calculate it GeometryFactory factory = new GeometryFactory(); Geometry p = factory.createPoint(c); boolean contains = g.contains(p); cacheResult(key, new Coordinate(c), contains, false, cached!=null, result, cache); return contains; } /** * Calculate if the input geometry contains the latitude and longitude point. * Calculate in a projection if needed * @param g Geometry in WGS84 * @param latitude * @param longitude * @param espg Can be null if just testing in lat-longs * @return */ public static boolean containsPoint(Geometry g, double latitude, double longitude, String espg){ // Get results object (for whole geometry) from cache if it exists CacheKey key = new CacheKey(g, espg); RecentlyUsedCache cache = ApplicationCache.singleton().get(ApplicationCache.PROJECTABLE_GEOMETRY_CONTAINS_CACHE); Object cached = cache.get(key); CachedResultRecord result = (CachedResultRecord)cached; // Create results object if needed GridTransforms transforms =null; if(result==null){ result = new CachedResultRecord(); // Transform if needed if(espg!=null){ transforms = GridTransforms.getAndCache(espg); result.projected = transforms.wgs84ToGrid(g); } } // Check if we already have the result Coordinate coordinate = new Coordinate(longitude, latitude, 0); if(result.results.containsKey(coordinate)){ return result.results.get(coordinate)==1; } // Calculate the contains, projecting as needed Geometry polygonGeom = g; GeometryFactory factory = new GeometryFactory(); Geometry pointGeom = factory.createPoint(new Coordinate(longitude, latitude)); if(transforms!=null){ polygonGeom = result.projected; pointGeom = transforms.wgs84ToGrid(pointGeom); } boolean contains = polygonGeom.contains(pointGeom); cacheResult(key, coordinate, contains, transforms!=null, cached!=null, result, cache); return contains; } private static void cacheResult(CacheKey key, Coordinate coordinate, boolean isContained, boolean hasTransform, boolean wasCached, CachedResultRecord cachedResultRecord, RecentlyUsedCache cache) { // If the results object is getting silly big, assume most of the results are old and clear them if(cachedResultRecord.results.size() > MAX_CACHE_POINT_SIZE_PER_RESULTS_OBJ){ cachedResultRecord.results.clear(); } // Add result to the results object cachedResultRecord.results.put(coordinate, isContained ? (byte)1 : (byte)0); // Remove existing results object from cache (if it was cached) as its size has changed if(wasCached){ cache.remove(key); } // Estimate size. We assume that the geometry is wholey owned by the cache record, // as typically we use contains for short-lived geometry (e.g. when geometry is edited and each edit is a geom) // which may only be referenced from here long nbBytes = Spatial.getEstimatedSizeInBytes(key.geometry); if(hasTransform){ nbBytes *=2; } long llSize = 3*8; long mapSize = cachedResultRecord.results.size() * (llSize + 8) + 64; nbBytes += mapSize; // Cache it cache.put(key, cachedResultRecord, nbBytes); } }