Overpass tile source (#665)

This commit is contained in:
Gustl22
2019-02-19 13:39:22 +01:00
committed by Emux
parent c0c8abe3a7
commit 23d65486e6
7 changed files with 454 additions and 19 deletions

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<T extends Builder<T>> extends UrlTileSource.Builder<T> {
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);
}
}

View File

@@ -1,6 +1,7 @@
/* /*
* Copyright 2013 Hannes Janetzek * Copyright 2013 Hannes Janetzek
* Copyright 2016 devemux86 * Copyright 2016 devemux86
* Copyright 2019 Gustl22
* *
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
* *
@@ -75,25 +76,36 @@ public class OverpassAPIReader {
private final String query; private final String query;
public OverpassAPIReader() {
this.query = null;
}
/** /**
* Creates a new instance with the specified geographical coordinates. * 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, 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) { 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) String bbox = "(" + Math.min(top, bottom) + "," + Math.min(left, right)
+ "," + Math.max(top, bottom) + "," + Math.max(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); System.out.println(msg);
} }
public OsmData getData() { public void parseInputStream() {
String encoded; String encoded;
try { try {
encoded = URLEncoder.encode(this.query, "utf-8"); encoded = URLEncoder.encode(this.query, "utf-8");
} catch (UnsupportedEncodingException e1) { } catch (UnsupportedEncodingException e1) {
e1.printStackTrace(); e1.printStackTrace();
return null; return;
} }
System.out.println(myBaseUrl + "?data=" + encoded); System.out.println(myBaseUrl + "?data=" + encoded);
@@ -363,6 +374,9 @@ public class OverpassAPIReader {
} }
inputStream = null; inputStream = null;
} }
}
public OsmData getData() {
for (Entry<OsmRelation, List<TmpRelation>> entry : relationMembersForRelation for (Entry<OsmRelation, List<TmpRelation>> entry : relationMembersForRelation
.entrySet()) { .entrySet()) {
@@ -392,8 +406,8 @@ public class OverpassAPIReader {
} }
} }
} }
log("nodes: " + ownNodes.size() + " ways: " + ownWays.size() /*log("nodes: " + ownNodes.size() + " ways: " + ownWays.size()
+ " relations: " + ownRelations.size()); + " relations: " + ownRelations.size());*/
// give up references to original collections // give up references to original collections
nodesById = null; nodesById = null;

View File

@@ -5,6 +5,7 @@ dependencies {
file("${rootDir}/vtm-desktop/natives").eachDir() { dir -> file("${rootDir}/vtm-desktop/natives").eachDir() { dir ->
implementation files(dir.path) implementation files(dir.path)
} }
implementation project(':vtm-extras')
implementation project(':vtm-gdx-poi3d') implementation project(':vtm-gdx-poi3d')
implementation project(':vtm-http') implementation project(':vtm-http')
implementation project(':vtm-jeo') implementation project(':vtm-jeo')

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -35,6 +35,7 @@ import org.oscim.renderer.bucket.RenderBuckets;
import org.oscim.theme.IRenderTheme; import org.oscim.theme.IRenderTheme;
import org.oscim.theme.styles.ExtrusionStyle; import org.oscim.theme.styles.ExtrusionStyle;
import org.oscim.theme.styles.RenderStyle; import org.oscim.theme.styles.RenderStyle;
import org.oscim.utils.geom.GeometryUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -53,6 +54,11 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim
*/ */
public static boolean POST_AA = false; 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. * Let vanish extrusions / meshes which are covered by others.
* {@link org.oscim.renderer.bucket.RenderBucket#EXTRUSION}: roofs are always translucent. * {@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); mBuildings.put(tile.hashCode(), buildingElements);
} }
element = new MapElement(element); // Deep copy, because element will be cleared 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)); buildingElements.add(new BuildingElement(element, extrusion));
return true; return true;
} }
@@ -216,8 +226,13 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim
// Search buildings which inherit parts // Search buildings which inherit parts
for (BuildingElement rootBuilding : tileBuildings) { for (BuildingElement rootBuilding : tileBuildings) {
if (rootBuilding.element.isBuildingPart() if (rootBuilding.element.isBuildingPart())
|| !(refId.equals(getValue(rootBuilding.element, Tag.KEY_ID)))) 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; continue;
rootBuildings.add(rootBuilding); rootBuildings.add(rootBuilding);

View File

@@ -27,6 +27,7 @@ import org.oscim.layers.tile.vector.VectorTileLayer;
import org.oscim.map.Map; import org.oscim.map.Map;
import org.oscim.theme.styles.ExtrusionStyle; import org.oscim.theme.styles.ExtrusionStyle;
import org.oscim.utils.ExtrusionUtils; import org.oscim.utils.ExtrusionUtils;
import org.oscim.utils.geom.GeometryUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -191,15 +192,20 @@ public class S3DBLayer extends BuildingLayer {
continue; continue;
String refId = getValue(partBuilding.element, Tag.KEY_REF); String refId = getValue(partBuilding.element, Tag.KEY_REF);
if (refId == null) if (!RAW_DATA && refId == null)
continue; continue;
TagSet partTags = partBuilding.element.tags; TagSet partTags = partBuilding.element.tags;
// Search buildings which inherit parts // Search buildings which inherit parts
for (BuildingElement rootBuilding : tileBuildings) { for (BuildingElement rootBuilding : tileBuildings) {
if (rootBuilding.element.isBuildingPart() if (rootBuilding.element.isBuildingPart())
|| !(refId.equals(rootBuilding.element.tags.getValue(Tag.KEY_ID)))) 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; continue;
if ((getValue(rootBuilding.element, Tag.KEY_ROOF_SHAPE) != null) if ((getValue(rootBuilding.element, Tag.KEY_ROOF_SHAPE) != null)