/* * This file is part of JGrasstools (http://www.jgrasstools.org) * (C) HydroloGIS - www.hydrologis.com * * JGrasstools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.jgrasstools.gears.modules.r.tmsgenerator; import static org.jgrasstools.gears.modules.r.tmsgenerator.MBTilesHelper.TILESIZE; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_AUTHORCONTACTS; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_AUTHORNAMES; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_DOCUMENTATION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_KEYWORDS; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_LABEL; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_LICENSE; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_NAME; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_STATUS; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_DO_LEGACY_GRASS_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_DO_LENIENT_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_IN_PATH_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_IN_RASTER_BOUNDS_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_IN_RASTER_FILE_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_IN_VECTOR_FILE_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_IN_WMS_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_P_CHECK_COLOR_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_P_EAST_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_P_EPSG_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_P_IMAGE_TYPE_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_P_MAX_ZOOM_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_P_MIN_ZOOM_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_P_NAME_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_P_NORTH_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_P_SOUTH_DESCRIPTION; import static org.jgrasstools.gears.i18n.GearsMessages.OMSTMSGENERATOR_P_WEST_DESCRIPTION; import java.awt.image.BufferedImage; import java.io.File; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import oms3.annotations.Author; import oms3.annotations.Description; import oms3.annotations.Documentation; import oms3.annotations.Execute; import oms3.annotations.In; import oms3.annotations.Keywords; import oms3.annotations.Label; import oms3.annotations.License; import oms3.annotations.Name; import oms3.annotations.Status; import oms3.annotations.UI; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.geometry.jts.JTS; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.jgrasstools.gears.io.vectorreader.OmsVectorReader; import org.jgrasstools.gears.libs.exceptions.ModelsIOException; import org.jgrasstools.gears.libs.exceptions.ModelsIllegalargumentException; import org.jgrasstools.gears.libs.exceptions.ModelsUserCancelException; import org.jgrasstools.gears.libs.modules.JGTConstants; import org.jgrasstools.gears.libs.modules.JGTModel; import org.jgrasstools.gears.utils.CrsUtilities; import org.jgrasstools.gears.utils.features.FeatureUtilities; import org.jgrasstools.gears.utils.files.FileUtilities; import org.jgrasstools.gears.utils.geometry.GeometryUtilities; import org.jgrasstools.gears.utils.images.ImageGenerator; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.geom.prep.PreparedGeometry; import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory; @Description(OMSTMSGENERATOR_DESCRIPTION) @Documentation(OMSTMSGENERATOR_DOCUMENTATION) @Author(name = OMSTMSGENERATOR_AUTHORNAMES, contact = OMSTMSGENERATOR_AUTHORCONTACTS) @Keywords(OMSTMSGENERATOR_KEYWORDS) @Label(OMSTMSGENERATOR_LABEL) @Name(OMSTMSGENERATOR_NAME) @Status(OMSTMSGENERATOR_STATUS) @License(OMSTMSGENERATOR_LICENSE) public class OmsTmsGenerator extends JGTModel { @Description(OMSTMSGENERATOR_IN_RASTER_FILE_DESCRIPTION) @UI(JGTConstants.FILEIN_UI_HINT) @In public String inRasterFile = null; @Description(OMSTMSGENERATOR_IN_RASTER_BOUNDS_DESCRIPTION) @In public List<GridGeometry2D> inRasterBounds = null; @Description(OMSTMSGENERATOR_IN_VECTOR_FILE_DESCRIPTION) @UI(JGTConstants.FILEIN_UI_HINT) @In public String inVectorFile = null; @Description(OMSTMSGENERATOR_IN_WMS_DESCRIPTION) @In public String inWMS = null; @Description(OMSTMSGENERATOR_P_NAME_DESCRIPTION) @In public String pName = "tmstiles"; @Description(OMSTMSGENERATOR_P_MIN_ZOOM_DESCRIPTION) @In public Integer pMinzoom = null; @Description(OMSTMSGENERATOR_P_MAX_ZOOM_DESCRIPTION) @In public Integer pMaxzoom = null; @Description(OMSTMSGENERATOR_P_NORTH_DESCRIPTION) @UI(JGTConstants.PROCESS_NORTH_UI_HINT) @In public Double pNorth = null; @Description(OMSTMSGENERATOR_P_SOUTH_DESCRIPTION) @UI(JGTConstants.PROCESS_SOUTH_UI_HINT) @In public Double pSouth = null; @Description(OMSTMSGENERATOR_P_WEST_DESCRIPTION) @UI(JGTConstants.PROCESS_WEST_UI_HINT) @In public Double pWest = null; @Description(OMSTMSGENERATOR_P_EAST_DESCRIPTION) @UI(JGTConstants.PROCESS_EAST_UI_HINT) @In public Double pEast = null; @Description(OMSTMSGENERATOR_P_EPSG_DESCRIPTION) @UI(JGTConstants.CRS_UI_HINT) @In public String pEpsg; @Description("An optional prj file to use instead of the epsg code.") @UI(JGTConstants.FILEIN_UI_HINT) @In public String inPrj; @Description("A shapefile to use to draw maps on zoom levels higher than pZoomLimit. Everything outside is not drawn.") @In public String inZoomLimitVector; @Description("The zoom limit above which the inZoomLimitVector is considered.") @In public double pZoomLimit = 17; @Description(OMSTMSGENERATOR_DO_LENIENT_DESCRIPTION) @In public boolean doLenient = true; @Description(OMSTMSGENERATOR_P_IMAGE_TYPE_DESCRIPTION) @In public int pImagetype = 0; @Description(OMSTMSGENERATOR_P_CHECK_COLOR_DESCRIPTION) @In public int[] pCheckcolor = new int[]{255, 255, 255}; @Description(OMSTMSGENERATOR_DO_LEGACY_GRASS_DESCRIPTION) @In public Boolean doLegacyGrass = false; @Description("Do mbtiles database.") @In public boolean doMbtiles = false; @Description(OMSTMSGENERATOR_IN_PATH_DESCRIPTION) @In public String inPath; private static final String EPSG_MERCATOR = "EPSG:3857"; private static final String EPSG_LATLONG = "EPSG:4326"; private PreparedGeometry zoomLimitGeometry; private MBTilesHelper mbtilesHelper; public CoordinateReferenceSystem dataCrs; private volatile boolean cancelModule = false; @Execute public void process() throws Exception { try { checkNull(inPath, pMinzoom, pMaxzoom, pWest, pEast, pSouth, pNorth); if (dataCrs == null) { if (pEpsg != null) { dataCrs = CrsUtilities.getCrsFromEpsg(pEpsg, null); } else { String wkt = FileUtilities.readFile(inPrj); dataCrs = CRS.parseWKT(wkt); } } String format = null; if (doMbtiles) { mbtilesHelper = new MBTilesHelper(); File dbFolder = new File(inPath); File dbFile = new File(dbFolder, pName + ".mbtiles"); ReferencedEnvelope dataBounds = new ReferencedEnvelope(pWest, pEast, pSouth, pNorth, dataCrs); MathTransform data2LLTransform = CRS.findMathTransform(dataCrs, DefaultGeographicCRS.WGS84); Envelope llEnvelope = JTS.transform(dataBounds, data2LLTransform); float n = (float) llEnvelope.getMaxY(); float s = (float) llEnvelope.getMinY(); float w = (float) llEnvelope.getMinX(); float e = (float) llEnvelope.getMaxX(); format = pImagetype == 0 ? "png" : "jpg"; mbtilesHelper.open(dbFile); mbtilesHelper.createTables(false); mbtilesHelper.fillMetadata(n, s, w, e, pName, format, pMinzoom, pMaxzoom); } int threads = getDefaultThreadsNum(); String ext = "png"; if (pImagetype == 1) { ext = "jpg"; } checkCancel(); List<String> inVectors = null; if (inVectorFile != null && new File(inVectorFile).exists()) inVectors = FileUtilities.readFileToLinesList(new File(inVectorFile)); checkCancel(); List<String> inRasters = null; if (inRasterFile != null && new File(inRasterFile).exists()) inRasters = FileUtilities.readFileToLinesList(new File(inRasterFile)); if (inRasters == null && inVectors == null) { throw new ModelsIllegalargumentException("No raster and vector input maps available. check your inputs.", this, pm); } if (dataCrs == null && pEpsg == null && inPrj == null) { throw new ModelsIllegalargumentException("No projection info available. check your inputs.", this, pm); } final CoordinateReferenceSystem mercatorCrs = CrsUtilities.getCrsFromEpsg(EPSG_MERCATOR, null); ReferencedEnvelope dataBounds = new ReferencedEnvelope(pWest, pEast, pSouth, pNorth, dataCrs); MathTransform data2MercatorTransform = CRS.findMathTransform(dataCrs, mercatorCrs); Envelope mercatorEnvelope = JTS.transform(dataBounds, data2MercatorTransform); ReferencedEnvelope mercatorBounds = new ReferencedEnvelope(mercatorEnvelope, mercatorCrs); checkCancel(); if (inZoomLimitVector != null) { SimpleFeatureCollection zoomLimitVector = OmsVectorReader.readVector(inZoomLimitVector); List<Geometry> geoms = FeatureUtilities.featureCollectionToGeometriesList(zoomLimitVector, true, null); MultiPolygon multiPolygon = gf.createMultiPolygon(geoms.toArray(GeometryUtilities.TYPE_POLYGON)); // convert to mercator Geometry multiPolygonGeom = JTS.transform(multiPolygon, data2MercatorTransform); zoomLimitGeometry = PreparedGeometryFactory.prepare(multiPolygonGeom); } File inFolder = new File(inPath); final File baseFolder = new File(inFolder, pName); final ImageGenerator imgGen = new ImageGenerator(pm, mercatorCrs); if (inWMS != null) { imgGen.setWMS(inWMS); } imgGen.doLegacyGrass = doLegacyGrass; String notLoading = "Not loading non-existing file: "; if (inRasters != null) for( String rasterPath : inRasters ) { File file = new File(rasterPath); if (file.exists()) { imgGen.addCoveragePath(rasterPath); } else { pm.errorMessage(notLoading + rasterPath); } } if (inRasterBounds != null) for( GridGeometry2D rasterBounds : inRasterBounds ) { imgGen.addCoverageRegion(rasterBounds); } if (inVectors != null) for( String vectorPath : inVectors ) { File file = new File(vectorPath); if (file.exists()) { imgGen.addFeaturePath(vectorPath, null); } else { pm.errorMessage(notLoading + vectorPath); } } imgGen.setLayers(); double w = mercatorBounds.getMinX(); double s = mercatorBounds.getMinY(); double e = mercatorBounds.getMaxX(); double n = mercatorBounds.getMaxY(); final GlobalMercator mercator = new GlobalMercator(); for( int z = pMinzoom; z <= pMaxzoom; z++ ) { checkCancel(); // get ul and lr tile number int[] llTileNumber = mercator.MetersToTile(w, s, z); int[] urTileNumber = mercator.MetersToTile(e, n, z); int startXTile = llTileNumber[0]; int startYTile = llTileNumber[1]; int endXTile = urTileNumber[0]; int endYTile = urTileNumber[1]; int tileNum = 0; final ReferencedEnvelope levelBounds = new ReferencedEnvelope(mercatorCrs); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(threads); pm.beginTask("Generating tiles at zoom level: " + z, (endXTile - startXTile + 1) * (endYTile - startYTile + 1)); for( int i = startXTile; i <= endXTile; i++ ) { checkCancel(); for( int j = startYTile; j <= endYTile; j++ ) { checkCancel(); double[] bounds = mercator.TileBounds(i, j, z); double west = bounds[0]; double south = bounds[1]; double east = bounds[2]; double north = bounds[3]; final ReferencedEnvelope tmpBounds = new ReferencedEnvelope(west, east, south, north, mercatorCrs); levelBounds.expandToInclude(tmpBounds); // if there is a zoom level geometry limitation, apply it if (zoomLimitGeometry != null && z > pZoomLimit) { double safeExtend = tmpBounds.getWidth() > tmpBounds.getHeight() ? tmpBounds.getWidth() : tmpBounds.getHeight(); final ReferencedEnvelope tmp = new ReferencedEnvelope(tmpBounds); tmp.expandBy(safeExtend); Polygon polygon = FeatureUtilities.envelopeToPolygon(tmp); if (!zoomLimitGeometry.intersects(polygon)) { pm.worked(1); continue; } } if (mbtilesHelper != null) { final int x = i; final int y = j; final int zz = z; final String fformat = format; tileNum++; Runnable runner = new Runnable(){ public void run() { if (!cancelModule) { try { checkCancel(); BufferedImage image = imgGen.getImageWithCheck(tmpBounds, TILESIZE, TILESIZE, 0.0, pCheckcolor); if (image != null) { mbtilesHelper.addTile(x, y, zz, image, fformat); } } catch (Exception e) { pm.errorMessage(e.getMessage()); cancelModule = true; } } pm.worked(1); } }; fixedThreadPool.execute(runner); } else { File imageFolder = new File(baseFolder, z + "/" + i); if (!imageFolder.exists()) { if (!imageFolder.mkdirs()) { throw new ModelsIOException("Unable to create folder:" + imageFolder, this); } } File ignoreMediaFile = new File(imageFolder, ".nomedia"); ignoreMediaFile.createNewFile(); final File imageFile = new File(imageFolder, j + "." + ext); if (imageFile.exists()) { pm.worked(1); continue; } tileNum++; final String imagePath = imageFile.getAbsolutePath(); final ReferencedEnvelope finalBounds = tmpBounds; Runnable runner = new Runnable(){ public void run() { if (!cancelModule) { try { if (pImagetype == 1) { imgGen.dumpJpgImage(imagePath, finalBounds, TILESIZE, TILESIZE, 0.0, pCheckcolor); } else { imgGen.dumpPngImage(imagePath, finalBounds, TILESIZE, TILESIZE, 0.0, pCheckcolor); } pm.worked(1); } catch (Exception ex) { pm.errorMessage(ex.getMessage()); cancelModule = true; } } } }; fixedThreadPool.execute(runner); } } } try { fixedThreadPool.shutdown(); while( !fixedThreadPool.isTerminated() ) { Thread.sleep(100); } } catch (InterruptedException exx) { exx.printStackTrace(); } pm.done(); pm.message("Zoom level: " + z + " has " + tileNum + " tiles."); // pm.message("Boundary covered at Zoom level: " + z + ": " + levelBounds); // pm.message("Total boundary wanted: " + mercatorBounds); } if (mbtilesHelper != null) { mbtilesHelper.createIndexes(); mbtilesHelper.close(); } else { CoordinateReferenceSystem latLongCrs = CrsUtilities.getCrsFromEpsg(EPSG_LATLONG, null); MathTransform transform = CRS.findMathTransform(mercatorCrs, latLongCrs); Envelope latLongBounds = JTS.transform(mercatorBounds, transform); Coordinate latLongCentre = latLongBounds.centre(); StringBuilder properties = new StringBuilder(); properties.append("url=").append(pName).append("/ZZZ/XXX/YYY.").append(ext).append("\n"); properties.append("minzoom=").append(pMinzoom).append("\n"); properties.append("maxzoom=").append(pMaxzoom).append("\n"); properties.append("center=").append(latLongCentre.y).append(" ").append(latLongCentre.x).append("\n"); properties.append("type=tms").append("\n"); File propFile = new File(inFolder, pName + ".mapurl"); FileUtilities.writeFile(properties.toString(), propFile); } } catch (ModelsUserCancelException e) { pm.errorMessage(ModelsUserCancelException.DEFAULTMESSAGE); } } }