From 23d65486e67d3fb9f2c38be80d125469d6ddb595 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Tue, 19 Feb 2019 13:39:22 +0100 Subject: [PATCH] Overpass tile source (#665) --- .../source/overpass/OverpassTileDecoder.java | 228 ++++++++++++++++++ .../source/overpass/OverpassTileSource.java | 83 +++++++ .../utils/overpass/OverpassAPIReader.java | 42 ++-- vtm-playground/build.gradle | 1 + .../src/org/oscim/test/OverpassTest.java | 88 +++++++ .../layers/tile/buildings/BuildingLayer.java | 19 +- .../layers/tile/buildings/S3DBLayer.java | 12 +- 7 files changed, 454 insertions(+), 19 deletions(-) create mode 100644 vtm-extras/src/org/oscim/tiling/source/overpass/OverpassTileDecoder.java create mode 100644 vtm-extras/src/org/oscim/tiling/source/overpass/OverpassTileSource.java create mode 100644 vtm-playground/src/org/oscim/test/OverpassTest.java diff --git a/vtm-extras/src/org/oscim/tiling/source/overpass/OverpassTileDecoder.java b/vtm-extras/src/org/oscim/tiling/source/overpass/OverpassTileDecoder.java new file mode 100644 index 00000000..13a98d41 --- /dev/null +++ b/vtm-extras/src/org/oscim/tiling/source/overpass/OverpassTileDecoder.java @@ -0,0 +1,228 @@ +/* + * Copyright 2014 Hannes Janetzek + * Copyright 2017 devemux86 + * Copyright 2019 Gustl22 + * + * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . + */ +package org.oscim.tiling.source.overpass; + +import org.oscim.core.GeometryBuffer.GeometryType; +import org.oscim.core.MapElement; +import org.oscim.core.Tag; +import org.oscim.core.TagSet; +import org.oscim.core.Tile; +import org.oscim.core.osm.OsmData; +import org.oscim.core.osm.OsmElement; +import org.oscim.core.osm.OsmNode; +import org.oscim.core.osm.OsmRelation; +import org.oscim.core.osm.OsmWay; +import org.oscim.tiling.ITileDataSink; +import org.oscim.tiling.source.ITileDecoder; +import org.oscim.tiling.source.mapfile.OSMUtils; +import org.oscim.utils.overpass.OverpassAPIReader; + +import java.io.IOException; +import java.io.InputStream; + +import static org.oscim.core.MercatorProjection.latitudeToY; +import static org.oscim.core.MercatorProjection.longitudeToX; + +public class OverpassTileDecoder implements ITileDecoder { + + private final MapElement mMapElement; + private ITileDataSink mTileDataSink; + + private double mTileY, mTileX, mTileScale; + + public OverpassTileDecoder() { + mMapElement = new MapElement(); + mMapElement.layer = 5; + } + + public synchronized boolean decode(Tile tile, ITileDataSink sink, InputStream is) { + mTileDataSink = sink; + mTileScale = 1 << tile.zoomLevel; + mTileX = tile.tileX / mTileScale; + mTileY = tile.tileY / mTileScale; + mTileScale *= Tile.SIZE; + + OsmData data; + try { + OverpassAPIReader reader = new OverpassAPIReader(); + reader.parse(is); + data = reader.getData(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + for (OsmNode element : data.getNodes()) + parseFeature(element); + for (OsmWay element : data.getWays()) + parseFeature(element); + for (OsmRelation element : data.getRelations()) + parseFeature(element); + + return true; + } + + private synchronized void parseFeature(OsmElement element) { + if (element.tags == null || element.tags.size() == 0) + return; + + synchronized (mMapElement) { + mMapElement.clear(); + mMapElement.tags.clear(); + + mMapElement.tags.set(element.tags); + //add tag information + decodeTags(mMapElement); + + parseGeometry(element); + + if (mMapElement.type == GeometryType.NONE) + return; + + mTileDataSink.process(mMapElement); + } + } + + private void parseGeometry(OsmElement element) { + //TODO mulipolygons + if (element instanceof OsmWay) { + boolean linearFeature = !OSMUtils.isArea(mMapElement); + if (linearFeature) { + mMapElement.type = GeometryType.LINE; + parseLine((OsmWay) element); + } else { + mMapElement.type = GeometryType.POLY; + parsePolygon((OsmWay) element); + } + } else if (element instanceof OsmNode) { + mMapElement.type = GeometryType.POINT; + mMapElement.startPoints(); + parseCoordinate((OsmNode) element); + } + } + + private void parsePolygon(OsmWay element) { + //int ring = 0; + + //for (element.rings) { + //if (ring == 0) + mMapElement.startPolygon(); + //else + // mMapElement.startHole(); + + //ring++; + parseCoordSequence(element); + removeLastPoint(); + //} + } + + private void removeLastPoint() { + mMapElement.pointNextPos -= 2; + mMapElement.index[mMapElement.indexCurrentPos] -= 2; + } + + private void parseLine(OsmWay element) { + mMapElement.startLine(); + parseCoordSequence(element); + } + + private void parseCoordSequence(OsmWay element) { + for (OsmNode node : element.nodes) + parseCoordinate(node); + } + + private void parseCoordinate(OsmNode element) { + mMapElement.addPoint((float) ((longitudeToX(element.lon) - mTileX) * mTileScale), + (float) ((latitudeToY(element.lat) - mTileY) * mTileScale)); + + } + + private void decodeTags(MapElement mapElement) { + TagSet tags = mapElement.tags; + Tag tag = tags.get(Tag.KEY_ROOF_DIRECTION); + if (tag != null) { + if (!isNumeric(tag.value)) { + switch (tag.value.toLowerCase()) { + case "n": + case "north": + tag.value = "0"; + break; + case "e": + case "east": + tag.value = "90"; + break; + case "s": + case "south": + tag.value = "180"; + break; + case "w": + case "west": + tag.value = "270"; + break; + + case "ne": + tag.value = "45"; + break; + case "se": + tag.value = "135"; + break; + case "sw": + tag.value = "225"; + break; + case "nw": + tag.value = "315"; + break; + + case "nne": + tag.value = "22"; + break; + case "ene": + tag.value = "67"; + break; + case "ese": + tag.value = "112"; + break; + case "sse": + tag.value = "157"; + break; + case "ssw": + tag.value = "202"; + break; + case "wsw": + tag.value = "247"; + break; + case "wnw": + tag.value = "292"; + break; + case "nnw": + tag.value = "337"; + break; + } + } + } + } + + private static boolean isNumeric(String str) { + try { + Float.parseFloat(str); + } catch (NumberFormatException nfe) { + return false; + } + return true; + } +} diff --git a/vtm-extras/src/org/oscim/tiling/source/overpass/OverpassTileSource.java b/vtm-extras/src/org/oscim/tiling/source/overpass/OverpassTileSource.java new file mode 100644 index 00000000..dcc9fb9d --- /dev/null +++ b/vtm-extras/src/org/oscim/tiling/source/overpass/OverpassTileSource.java @@ -0,0 +1,83 @@ +/* + * Copyright 2013 Hannes Janetzek + * Copyright 2018 devemux86 + * Copyright 2019 Gustl22 + * + * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . + */ +package org.oscim.tiling.source.overpass; + +import org.oscim.core.BoundingBox; +import org.oscim.core.Tile; +import org.oscim.tiling.ITileDataSource; +import org.oscim.tiling.OverzoomTileDataSource; +import org.oscim.tiling.source.UrlTileDataSource; +import org.oscim.tiling.source.UrlTileSource; +import org.oscim.utils.overpass.OverpassAPIReader; + +public class OverpassTileSource extends UrlTileSource { + + private static final String DEFAULT_URL = "https://www.overpass-api.de/api/interpreter?data=[out:json];"; + private static final String DEFAULT_PATH = "(node{{bbox}};way{{bbox}};>;);out%20body;"; + + public static class Builder> extends UrlTileSource.Builder { + + public Builder() { + super(DEFAULT_URL, DEFAULT_PATH); + zoomMax(17); + } + + @Override + public OverpassTileSource build() { + return new OverpassTileSource(this); + } + } + + @SuppressWarnings("rawtypes") + public static Builder builder() { + return new Builder(); + } + + public OverpassTileSource(Builder builder) { + super(builder); + + setUrlFormatter(new TileUrlFormatter() { + @Override + public String formatTilePath(UrlTileSource tileSource, Tile tile) { + BoundingBox bb = tile.getBoundingBox(); + + String query = OverpassAPIReader.query( + bb.getMinLongitude(), + bb.getMaxLongitude(), + bb.getMaxLatitude(), + bb.getMinLatitude(), + DEFAULT_PATH); + + /*String encoded; + try { + query = URLEncoder.encode(query, "utf-8"); + } catch (UnsupportedEncodingException e1) { + e1.printStackTrace(); + return null; + }*/ + return query; + } + }); + } + + @Override + public ITileDataSource getDataSource() { + return new OverzoomTileDataSource(new UrlTileDataSource(this, new OverpassTileDecoder(), getHttpEngine()), mOverZoom); + } +} diff --git a/vtm-extras/src/org/oscim/utils/overpass/OverpassAPIReader.java b/vtm-extras/src/org/oscim/utils/overpass/OverpassAPIReader.java index c831d678..9dd6e567 100644 --- a/vtm-extras/src/org/oscim/utils/overpass/OverpassAPIReader.java +++ b/vtm-extras/src/org/oscim/utils/overpass/OverpassAPIReader.java @@ -1,6 +1,7 @@ /* * Copyright 2013 Hannes Janetzek * Copyright 2016 devemux86 + * Copyright 2019 Gustl22 * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -75,25 +76,36 @@ public class OverpassAPIReader { private final String query; + public OverpassAPIReader() { + this.query = null; + } + /** * Creates a new instance with the specified geographical coordinates. - * - * @param left The longitude marking the left edge of the bounding box. - * @param right The longitude marking the right edge of the bounding box. - * @param top The latitude marking the top edge of the bounding box. - * @param bottom The latitude marking the bottom edge of the bounding box. - * @param baseUrl (optional) The base url of the server (eg. - * http://www.openstreetmap.org/api/0.5). */ public OverpassAPIReader(final double left, final double right, - final double top, final double bottom, final String baseUrl, + final double top, final double bottom, final String query) { + this.query = query(left, right, top, bottom, query); + } + + /** + * @param left The longitude marking the left edge of the bounding box. + * @param right The longitude marking the right edge of the bounding box. + * @param top The latitude marking the top edge of the bounding box. + * @param bottom The latitude marking the bottom edge of the bounding box. + * @param query The prepared query. + * @return the processed query with specified bounding box + */ + public static String query(final double left, final double right, + final double top, final double bottom, + final String query) { String bbox = "(" + Math.min(top, bottom) + "," + Math.min(left, right) + "," + Math.max(top, bottom) + "," + Math.max(left, right) + ")"; - this.query = query.replaceAll("\\{\\{bbox\\}\\}", bbox); + return query.replaceAll("\\{\\{bbox\\}\\}", bbox); } @@ -335,14 +347,13 @@ public class OverpassAPIReader { System.out.println(msg); } - public OsmData getData() { - + public void parseInputStream() { String encoded; try { encoded = URLEncoder.encode(this.query, "utf-8"); } catch (UnsupportedEncodingException e1) { e1.printStackTrace(); - return null; + return; } System.out.println(myBaseUrl + "?data=" + encoded); @@ -363,6 +374,9 @@ public class OverpassAPIReader { } inputStream = null; } + } + + public OsmData getData() { for (Entry> entry : relationMembersForRelation .entrySet()) { @@ -392,8 +406,8 @@ public class OverpassAPIReader { } } } - log("nodes: " + ownNodes.size() + " ways: " + ownWays.size() - + " relations: " + ownRelations.size()); + /*log("nodes: " + ownNodes.size() + " ways: " + ownWays.size() + + " relations: " + ownRelations.size());*/ // give up references to original collections nodesById = null; diff --git a/vtm-playground/build.gradle b/vtm-playground/build.gradle index a0458d78..81b7ced8 100644 --- a/vtm-playground/build.gradle +++ b/vtm-playground/build.gradle @@ -5,6 +5,7 @@ dependencies { file("${rootDir}/vtm-desktop/natives").eachDir() { dir -> implementation files(dir.path) } + implementation project(':vtm-extras') implementation project(':vtm-gdx-poi3d') implementation project(':vtm-http') implementation project(':vtm-jeo') diff --git a/vtm-playground/src/org/oscim/test/OverpassTest.java b/vtm-playground/src/org/oscim/test/OverpassTest.java new file mode 100644 index 00000000..a3a8ce3e --- /dev/null +++ b/vtm-playground/src/org/oscim/test/OverpassTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2016-2018 devemux86 + * Copyright 2019 Gustl22 + * + * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . + */ +package org.oscim.test; + +import org.oscim.core.MapPosition; +import org.oscim.gdx.GdxMapApp; +import org.oscim.layers.GroupLayer; +import org.oscim.layers.tile.bitmap.BitmapTileLayer; +import org.oscim.layers.tile.buildings.BuildingLayer; +import org.oscim.layers.tile.buildings.S3DBLayer; +import org.oscim.layers.tile.vector.VectorTileLayer; +import org.oscim.layers.tile.vector.labeling.LabelLayer; +import org.oscim.map.Map; +import org.oscim.map.Viewport; +import org.oscim.theme.VtmThemes; +import org.oscim.tiling.TileSource; +import org.oscim.tiling.source.OkHttpEngine; +import org.oscim.tiling.source.bitmap.DefaultSources; +import org.oscim.tiling.source.overpass.OverpassTileSource; + +/** + * Use Overpass API data for vector layer. + * Only for developing as can be error-prone. + * Take care of overpass provider licenses. + */ +public class OverpassTest extends GdxMapApp { + + @Override + public void createLayers() { + Map map = getMap(); + + TileSource tileSource = OverpassTileSource.builder() + .httpFactory(new OkHttpEngine.OkHttpFactory()) + .zoomMin(15) + .zoomMax(17) + .build(); + VectorTileLayer l = map.setBaseMap(tileSource); + + TileSource bitmapTileSource = DefaultSources.OPENSTREETMAP + .httpFactory(new OkHttpEngine.OkHttpFactory()) + .zoomMax(15) + .fadeSteps(new BitmapTileLayer.FadeStep[]{ + new BitmapTileLayer.FadeStep(15, 16, 1f, 0f), + new BitmapTileLayer.FadeStep(16, Viewport.MAX_ZOOM_LEVEL, 0f, 0f) + }) + .build(); + mMap.layers().add(new BitmapTileLayer(mMap, bitmapTileSource)); + + BuildingLayer.RAW_DATA = true; + GroupLayer groupLayer = new GroupLayer(mMap); + groupLayer.layers.add(new S3DBLayer(map, l)); + groupLayer.layers.add(new LabelLayer(map, l)); + map.layers().add(groupLayer); + + map.setTheme(VtmThemes.DEFAULT); + MapPosition pos = MapPreferences.getMapPosition(); + if (pos != null) + map.setMapPosition(pos); + else + map.setMapPosition(53.075, 8.808, 1 << 17); + } + + @Override + public void dispose() { + MapPreferences.saveMapPosition(mMap.getMapPosition()); + super.dispose(); + } + + public static void main(String[] args) { + GdxMapApp.init(); + GdxMapApp.run(new OverpassTest()); + } +} diff --git a/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java b/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java index 4c2dfe75..e3dd4b51 100644 --- a/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java +++ b/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java @@ -35,6 +35,7 @@ import org.oscim.renderer.bucket.RenderBuckets; import org.oscim.theme.IRenderTheme; import org.oscim.theme.styles.ExtrusionStyle; import org.oscim.theme.styles.RenderStyle; +import org.oscim.utils.geom.GeometryUtils; import java.util.ArrayList; import java.util.HashMap; @@ -53,6 +54,11 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim */ public static boolean POST_AA = false; + /** + * Use real time calculations to pre-process data. + */ + public static boolean RAW_DATA = false; + /** * Let vanish extrusions / meshes which are covered by others. * {@link org.oscim.renderer.bucket.RenderBucket#EXTRUSION}: roofs are always translucent. @@ -147,6 +153,10 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim mBuildings.put(tile.hashCode(), buildingElements); } element = new MapElement(element); // Deep copy, because element will be cleared + if (RAW_DATA && element.isClockwise() > 0) { + // Buildings must be counter clockwise in VTM (mirrored to OSM) + element.reverse(); + } buildingElements.add(new BuildingElement(element, extrusion)); return true; } @@ -216,8 +226,13 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim // Search buildings which inherit parts for (BuildingElement rootBuilding : tileBuildings) { - if (rootBuilding.element.isBuildingPart() - || !(refId.equals(getValue(rootBuilding.element, Tag.KEY_ID)))) + if (rootBuilding.element.isBuildingPart()) + continue; + if (RAW_DATA) { + float[] center = GeometryUtils.center(partBuilding.element.points, 0, partBuilding.element.pointNextPos, null); + if (!GeometryUtils.pointInPoly(center[0], center[1], rootBuilding.element.points, rootBuilding.element.index[0], 0)) + continue; + } else if (!(refId.equals(getValue(rootBuilding.element, Tag.KEY_ID)))) continue; rootBuildings.add(rootBuilding); diff --git a/vtm/src/org/oscim/layers/tile/buildings/S3DBLayer.java b/vtm/src/org/oscim/layers/tile/buildings/S3DBLayer.java index 77ed8959..9279edca 100644 --- a/vtm/src/org/oscim/layers/tile/buildings/S3DBLayer.java +++ b/vtm/src/org/oscim/layers/tile/buildings/S3DBLayer.java @@ -27,6 +27,7 @@ import org.oscim.layers.tile.vector.VectorTileLayer; import org.oscim.map.Map; import org.oscim.theme.styles.ExtrusionStyle; import org.oscim.utils.ExtrusionUtils; +import org.oscim.utils.geom.GeometryUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -191,15 +192,20 @@ public class S3DBLayer extends BuildingLayer { continue; String refId = getValue(partBuilding.element, Tag.KEY_REF); - if (refId == null) + if (!RAW_DATA && refId == null) continue; TagSet partTags = partBuilding.element.tags; // Search buildings which inherit parts for (BuildingElement rootBuilding : tileBuildings) { - if (rootBuilding.element.isBuildingPart() - || !(refId.equals(rootBuilding.element.tags.getValue(Tag.KEY_ID)))) + if (rootBuilding.element.isBuildingPart()) + continue; + if (RAW_DATA) { + float[] center = GeometryUtils.center(partBuilding.element.points, 0, partBuilding.element.pointNextPos, null); + if (!GeometryUtils.pointInPoly(center[0], center[1], rootBuilding.element.points, rootBuilding.element.index[0], 0)) + continue; + } else if (!refId.equals(rootBuilding.element.tags.getValue(Tag.KEY_ID))) continue; if ((getValue(rootBuilding.element, Tag.KEY_ROOF_SHAPE) != null)