首页 > 解决方案 > Geotools GTRender 创建错位的瓷砖

问题描述

如果已经根据 geotools 框架中的 GTRenderer 组合了一个 tilerenderer。

问题是这些瓷砖包含高度偏移(见图)。

在此处输入图像描述

这是我的代码:

   import org.geotools.data.DataStore;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.FeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.FeatureLayer;
import org.geotools.map.Layer;
import org.geotools.map.MapContent;
import org.geotools.map.MapViewport;
import org.geotools.referencing.CRS;
import org.geotools.referencing.wkt.Parser;
import org.geotools.renderer.GTRenderer;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.*;
import org.geotools.xml.styling.SLDParser;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.swing.plaf.synth.ColorType;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;

import java.util.Map;
/**
 * This Renderer uses the geotools library to render the world shape and city names as tile png image.
 * */
@Component
public class WorldRasterTileRenderer {
    private static final Logger logger = LoggerFactory.getLogger(WorldRasterTileRenderer.class);
    private MapContent map;
    private GTRenderer renderer;
    private Rectangle tilePixelSize = new Rectangle(0, 0, 256, 256);
    private CoordinateReferenceSystem crs;

    @PostConstruct
    public void init(){
        try {
            map = createMapContent();
            renderer = new StreamingRenderer();
            renderer.setMapContent(map);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    private MapContent createMapContent() throws Exception {
        MapContent map = new MapContent();
        map.setTitle("WorldMap");
        //create country layer
        FeatureSource<SimpleFeatureType, SimpleFeature> featureSourceCountry = readShapefile("/country_lines.shp");
        Style style = createStyle(featureSourceCountry);
        Layer countryLayer = new FeatureLayer(featureSourceCountry, style,"country");
        map.addLayer(countryLayer);
        
        MapViewport mapViewport = new MapViewport();
        //mapViewport.setCoordinateReferenceSystem(crs);
        map.setViewport(mapViewport);

        return map;
    }
    private FeatureSource<SimpleFeatureType, SimpleFeature> readShapefile(String path) throws IOException, FactoryException {
        File file = new File(path);
        Map<String, Object> filemap = new HashMap<>();
        filemap.put("url", file.toURI().toURL());

        DataStore dataStore = DataStoreFinder.getDataStore(filemap);
        String typeName = dataStore.getTypeNames()[0];
        FeatureSource<SimpleFeatureType, SimpleFeature> source = dataStore.getFeatureSource(typeName);
        SimpleFeatureType schema = source.getSchema();

        return source;
    }

    public synchronized byte[] renderRasterTile(int x, int y, int z){
        Envelope tileBounds = this.getTileBounds(x,y,z);

        BufferedImage image = new BufferedImage(tilePixelSize.width, tilePixelSize.height, BufferedImage.TYPE_INT_RGB);

        Graphics2D gr = image.createGraphics();
        gr.setPaint(Color.decode("#b8dee6"));
        gr.fill(tilePixelSize);

        try {
            renderer.paint(gr, tilePixelSize, tileBounds);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write( image, "png", baos );
            baos.flush();
            byte[] imageInByte = baos.toByteArray();
            baos.close();
            return imageInByte;
        } catch (IOException e) {
            throw new RuntimeException(e);

        }
    }
    private Style loadStyleFromXml() throws Exception {
        java.net.URL base = getClass().getResource("rs-testData");

        StyleFactory factory = CommonFactoryFinder.getStyleFactory();
        java.net.URL surl = new java.net.URL(base + "/markTest.sld");

        SLDParser stylereader = new SLDParser( factory, surl);
        Style styles[] = stylereader.readXML();
        return styles[0];
    }
    public Envelope getTileBounds(int x, int y, int zoom)
    {
        return new Envelope(this.getLong(x, zoom), this.getLong(x + 1, zoom), this.getLat(y,zoom), this.getLat(y+1, zoom));   
    }

    public double getLong(int x, int zoom)
    {
        return ( x / Math.pow(2, zoom) * 360 - 180 );
    }

    public double getLat(int y, int zoom)
    {
        double r2d = 180 / Math.PI;
        double n = Math.PI - 2 * Math.PI * y / Math.pow(2, zoom);
        return r2d * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
    }
}

在代码中,所有投影都应该是 WGS84。

它必须是某种投影问题,但在 EPSG:3857 中具有形状源确实会导致空瓷砖。即使我将 MapViewport 更改为相同的投影。

你知道我可能会错过什么吗?

标签: javashapefiletilegeotools

解决方案


我不能确切地说出了什么问题,但是当我将它与OSM 瓷砖gt-tile-client代码中的计算进行比较时,你计算瓷砖的纬度、经度的方式看起来有点不对劲:

public Tile findTileAtCoordinate(double lon, double lat, ZoomLevel zoomLevel,
        TileService service) {
    lat = TileFactory.normalizeDegreeValue(lat, 90);
    lon = TileFactory.normalizeDegreeValue(lon, 180);

    /**
     * Because the latitude is only valid in 85.0511 °N to 85.0511 °S (http://wiki.openstreetmap.org/wiki/Tilenames#X_and_Y), we have to correct
     * if necessary.
     */
    lat = OSMTileFactory.moveInRange(lat, WebMercatorTileService.MIN_LATITUDE,
            WebMercatorTileService.MAX_LATITUDE);

    int xTile = (int) Math.floor((lon + 180) / 360 * (1 << zoomLevel.getZoomLevel()));
    int yTile = (int) Math.floor(
            (1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180))
                    / Math.PI) / 2 * (1 << zoomLevel.getZoomLevel()));
    if (yTile < 0)
        yTile = 0;
    return new OSMTile(xTile, yTile, zoomLevel, service);
}

推荐阅读