/******************************************************************************* * Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com) * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v3 * which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt ******************************************************************************/ package com.opendoorlogistics.core.geometry; import java.io.File; import java.util.Map; import org.geotools.referencing.CRS; import org.geotools.referencing.ReferencingFactoryFinder; import org.opengis.referencing.crs.CRSAuthorityFactory; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import com.opendoorlogistics.api.geometry.ODLGeom; import com.opendoorlogistics.api.tables.ODLColumnType; import com.opendoorlogistics.api.tables.ODLDatastore; import com.opendoorlogistics.api.tables.ODLDatastoreAlterable; import com.opendoorlogistics.api.tables.ODLTableAlterable; import com.opendoorlogistics.api.tables.ODLTableReadOnly; import com.opendoorlogistics.core.AppProperties; import com.opendoorlogistics.core.cache.ApplicationCache; import com.opendoorlogistics.core.cache.RecentlyUsedCache; import com.opendoorlogistics.core.tables.memory.ODLDatastoreImpl; import com.opendoorlogistics.core.tables.utils.SizesInBytesEstimator; import com.opendoorlogistics.core.tables.utils.TableUtils; import com.opendoorlogistics.core.utils.SimpleSoftReferenceMap; import com.vividsolutions.jts.geom.Geometry; /** * Performs one off initialisation of geotools components which must be * called before any used and stores any singleton components. * @author Phil * */ public final class Spatial { private static boolean init=false; private static CoordinateReferenceSystem wgs84crs; private static CRSAuthorityFactory crsFac; private static final SimpleSoftReferenceMap<ShapefileLink,ODLGeom> shapefileLinkCache = new SimpleSoftReferenceMap<>(100); private static final double SPATIAL_RENDERER_SIMPLIFY_DISTANCE_TOLERANCE; private static final double SPATIAL_RENDERER_SIMPLIFY_DISTANCE_TOLERANCE_LINESTRING; // private static final SimpleSoftReferenceMap<File, ODLDatastore<? extends ODLTableReadOnly>> shapefileLookupCache = new SimpleSoftReferenceMap<>(); public static synchronized void initSpatial(){ if(!init){ // ensure geotools uses longitude, latitude order, not latitude, longitude, in the entire application System.setProperty("org.geotools.referencing.forceXY", "true"); crsFac = ReferencingFactoryFinder.getCRSAuthorityFactory("EPSG", null); try { wgs84crs = crsFac.createCoordinateReferenceSystem("4326"); } catch (Throwable e) { throw new RuntimeException(e); } } } public static CoordinateReferenceSystem wgs84(){ initSpatial(); return wgs84crs; } /** * Create math transform to go from WGS84 to the input EPSG SRID system * @param espg_srid * @return */ public static MathTransform fromWGS84(String espg_srid){ try { CoordinateReferenceSystem crs = crsFac.createCoordinateReferenceSystem(espg_srid); return CRS.findMathTransform(wgs84crs,crs ,true); } catch (Throwable e) { throw new RuntimeException(e); } } /** * Get transform to turn other coord system into WGS84 * @param crs * @return */ public static MathTransform toWgs84(CoordinateReferenceSystem crs){ initSpatial(); try { return CRS.findMathTransform(crs, wgs84crs,true); // return new DefaultCoordinateOperationFactory().createOperation(wgs84crs, crs); } catch (Throwable e) { throw new RuntimeException(e); } } static synchronized ODLGeom loadLink(ShapefileLink link){ initSpatial(); ODLGeom ret = shapefileLinkCache.get(link); if(ret==null){ // parse and cache the entire file as we would normally use many objects in the same file for(Map.Entry<ShapefileLink, ODLGeom> entry : ImportShapefile.importShapefile(new File(link.getFile()),false, null,true).entrySet()){ if(entry.getValue()!=null){ ShapefileLink importedLink = entry.getKey(); ODLGeom gwc = entry.getValue(); if(link.equals(importedLink)){ ret = gwc; } cacheLink(importedLink, gwc); } } } return ret; } private synchronized static void cacheLink(ShapefileLink link, ODLGeom gwc) { if(shapefileLinkCache.get(link)==null){ shapefileLinkCache.put(link, gwc); } } /** * Looks up a value in the shapefile and returns the geometry for it * @param filename Shapefile filename. * @param searchvalue Value to search for. * @param type Typename in the file to search within. * @param searchfield Field within the type to search within. * @return */ @SuppressWarnings("unchecked") static public synchronized ODLGeomImpl lookupshapefile(String filename,String searchvalue, String type, String searchfield ){ Spatial.initSpatial(); // try getting the shapefile from cache first boolean ok = true; ODLDatastore<? extends ODLTableReadOnly> ds=null; File file = new File(filename); ds = (ODLDatastore<? extends ODLTableReadOnly>)shapefileCache().get(file); if(ds==null){ ds = importAndCacheShapefile(file); } // find table - only need to use type if we have more than one table which should probably never happen... ODLTableReadOnly table=ds.getTableCount()==1 ? ds.getTableAt(0): TableUtils.findTable(ds, type); ok = table!=null; // get geometry field int geomIndx = -1; if(ok){ geomIndx = TableUtils.findColumnIndx(table, ODLColumnType.GEOM); ok = geomIndx!=-1; } // find the search column index int searchIndx=-1; if(ok){ searchIndx = TableUtils.findColumnIndx(table, searchfield); ok = searchIndx!=-1; } // find the value if(ok){ long[] list = table.find(searchIndx, searchvalue); if(list.length>0){ return (ODLGeomImpl) table.getValueById(list[0], geomIndx); } } return null; } /** * @param file * @return */ public static ODLDatastoreAlterable<ODLTableAlterable> importAndCacheShapefile(File file) { ODLDatastoreAlterable<ODLTableAlterable> alterableDs = ODLDatastoreImpl.alterableFactory.create(); ImportShapefile.importShapefile(file,false,alterableDs,false); if(alterableDs.getTableCount()>0){ long size = SizesInBytesEstimator.estimateBytes(alterableDs); shapefileCache().put(file, alterableDs,size); } return alterableDs; } static{ initSpatial(); // load simplify tolerance from properties SPATIAL_RENDERER_SIMPLIFY_DISTANCE_TOLERANCE = AppProperties.getDouble(AppProperties.SPATIAL_RENDERER_SIMPLIFY_DISTANCE_TOLERANCE,0.0); SPATIAL_RENDERER_SIMPLIFY_DISTANCE_TOLERANCE_LINESTRING = AppProperties.getDouble(AppProperties.SPATIAL_RENDERER_SIMPLIFY_DISTANCE_TOLERANCE_LINESTRING,0.0); } private static RecentlyUsedCache shapefileCache(){ return ApplicationCache.singleton().get(ApplicationCache.IMPORTED_SHAPEFILE_CACHE); } public static long getEstimatedSizeInBytes(Geometry geom) { // estimate size of geometry in bytes int size=0; size += 4*8; // geometries store an envelope object size += geom.getNumPoints() * 3 * 8; // all points (point has 3 doubles - x, y, z) size += 100; // add some extra to account for pointers etc return size; } /** * Get the application-wide simplification tolerance limit in pixels, which is set via user properties * @return */ public static double getRendererSimplifyDistanceTolerance(){ return SPATIAL_RENDERER_SIMPLIFY_DISTANCE_TOLERANCE; } public static double getRendererSimplifyDistanceToleranceLineString(){ return SPATIAL_RENDERER_SIMPLIFY_DISTANCE_TOLERANCE_LINESTRING; } }