Added mvt parser lib. Added mapillary vector tiles. Refactored resources cache.
This commit is contained in:
parent
3e69f02629
commit
ba63945038
40 changed files with 7937 additions and 356 deletions
BIN
OsmAnd-java/libs/jts-core-1.14.0.jar
Normal file
BIN
OsmAnd-java/libs/jts-core-1.14.0.jar
Normal file
Binary file not shown.
|
@ -0,0 +1,50 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.adapt.jts;
|
||||||
|
|
||||||
|
import com.vividsolutions.jts.geom.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter {@link Polygon} and {@link MultiPolygon} by area or
|
||||||
|
* {@link LineString} and {@link MultiLineString} by length.
|
||||||
|
*
|
||||||
|
* @see IGeometryFilter
|
||||||
|
*/
|
||||||
|
public final class GeomMinSizeFilter implements IGeometryFilter {
|
||||||
|
|
||||||
|
/** Minimum area */
|
||||||
|
private final double minArea;
|
||||||
|
|
||||||
|
/** Minimum length */
|
||||||
|
private final double minLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param minArea minimum area required for a {@link Polygon} or {@link MultiPolygon}
|
||||||
|
* @param minLength minimum length required for a {@link LineString} or {@link MultiLineString}
|
||||||
|
*/
|
||||||
|
public GeomMinSizeFilter(double minArea, double minLength) {
|
||||||
|
if(minArea < 0.0d) {
|
||||||
|
throw new IllegalArgumentException("minArea must be >= 0");
|
||||||
|
}
|
||||||
|
if(minLength < 0.0d) {
|
||||||
|
throw new IllegalArgumentException("minLength must be >= 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.minArea = minArea;
|
||||||
|
this.minLength = minLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(Geometry geometry) {
|
||||||
|
boolean accept = true;
|
||||||
|
|
||||||
|
if((geometry instanceof Polygon || geometry instanceof MultiPolygon)
|
||||||
|
&& geometry.getArea() < minArea) {
|
||||||
|
accept = false;
|
||||||
|
|
||||||
|
} else if((geometry instanceof LineString || geometry instanceof MultiLineString)
|
||||||
|
&& geometry.getLength() < minLength) {
|
||||||
|
accept = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accept;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.adapt.jts;
|
||||||
|
|
||||||
|
import com.vividsolutions.jts.geom.Geometry;
|
||||||
|
|
||||||
|
public interface IGeometryFilter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the value should be accepted (pass), or false if the value should be rejected (fail).
|
||||||
|
*
|
||||||
|
* @param geometry input to test
|
||||||
|
* @return true if the value should be accepted (pass), or false if the value should be rejected (fail)
|
||||||
|
* @see Geometry
|
||||||
|
*/
|
||||||
|
boolean accept(Geometry geometry);
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.adapt.jts;
|
||||||
|
|
||||||
|
import net.osmand.binary.VectorTile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process MVT tags and feature id, convert to user data object. The returned user data
|
||||||
|
* object may be null.
|
||||||
|
*/
|
||||||
|
public interface ITagConverter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert MVT user data to JTS user data object or null.
|
||||||
|
*
|
||||||
|
* @param id feature id, may be {@code null}
|
||||||
|
* @param tags MVT feature tags, may be invalid
|
||||||
|
* @param keysList layer key list
|
||||||
|
* @param valuesList layer value list
|
||||||
|
* @return user data object or null
|
||||||
|
*/
|
||||||
|
Object toUserData(Long id,
|
||||||
|
List<Integer> tags,
|
||||||
|
List<String> keysList,
|
||||||
|
List<VectorTile.Tile.Value> valuesList);
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.adapt.jts;
|
||||||
|
|
||||||
|
import com.wdtinc.mapbox_vector_tile.build.MvtLayerProps;
|
||||||
|
|
||||||
|
import net.osmand.binary.VectorTile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a user data object, converts to MVT feature tags.
|
||||||
|
*/
|
||||||
|
public interface IUserDataConverter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Convert user data to MVT tags. The supplied user data may be null. Implementation
|
||||||
|
* should update layerProps and optionally set the feature id.</p>
|
||||||
|
*
|
||||||
|
* <p>SIDE EFFECT: The implementation may add tags to featureBuilder, modify layerProps, modify userData.</p>
|
||||||
|
*
|
||||||
|
* @param userData user object may contain values in any format; may be null
|
||||||
|
* @param layerProps properties global to the layer the feature belongs to
|
||||||
|
* @param featureBuilder may be modified to contain additional tags
|
||||||
|
*/
|
||||||
|
void addTags(Object userData, MvtLayerProps layerProps, VectorTile.Tile.Feature.Builder featureBuilder);
|
||||||
|
}
|
|
@ -0,0 +1,655 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.adapt.jts;
|
||||||
|
|
||||||
|
import com.vividsolutions.jts.algorithm.CGAlgorithms;
|
||||||
|
import com.vividsolutions.jts.geom.Coordinate;
|
||||||
|
import com.vividsolutions.jts.geom.CoordinateArrays;
|
||||||
|
import com.vividsolutions.jts.geom.Envelope;
|
||||||
|
import com.vividsolutions.jts.geom.Geometry;
|
||||||
|
import com.vividsolutions.jts.geom.GeometryCollection;
|
||||||
|
import com.vividsolutions.jts.geom.GeometryFactory;
|
||||||
|
import com.vividsolutions.jts.geom.LineString;
|
||||||
|
import com.vividsolutions.jts.geom.MultiLineString;
|
||||||
|
import com.vividsolutions.jts.geom.MultiPoint;
|
||||||
|
import com.vividsolutions.jts.geom.MultiPolygon;
|
||||||
|
import com.vividsolutions.jts.geom.Point;
|
||||||
|
import com.vividsolutions.jts.geom.Polygon;
|
||||||
|
import com.vividsolutions.jts.geom.TopologyException;
|
||||||
|
import com.vividsolutions.jts.geom.util.AffineTransformation;
|
||||||
|
import com.vividsolutions.jts.simplify.TopologyPreservingSimplifier;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.build.MvtLayerParams;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.build.MvtLayerProps;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.encoding.GeomCmd;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.encoding.GeomCmdHdr;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.encoding.MvtUtil;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.encoding.ZigZag;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.util.Vec2d;
|
||||||
|
|
||||||
|
import net.osmand.binary.VectorTile;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapt JTS {@link Geometry} to 'Mapbox Vector Tile' objects.
|
||||||
|
*/
|
||||||
|
public final class JtsAdapter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create geometry clipped and then converted to MVT 'extent' coordinates. Result
|
||||||
|
* contains both clipped geometry (intersection) and transformed geometry for encoding to MVT.
|
||||||
|
*
|
||||||
|
* @param g original 'source' geometry
|
||||||
|
* @param tileEnvelope world coordinate bounds for tile
|
||||||
|
* @param geomFactory creates a geometry for the tile envelope
|
||||||
|
* @param mvtLayerParams specifies vector tile properties
|
||||||
|
* @param filter geometry values that fail filter after transforms are removed
|
||||||
|
* @return tile geometry result
|
||||||
|
* @see TileGeomResult
|
||||||
|
*/
|
||||||
|
public static TileGeomResult createTileGeom(Geometry g,
|
||||||
|
Envelope tileEnvelope,
|
||||||
|
GeometryFactory geomFactory,
|
||||||
|
MvtLayerParams mvtLayerParams,
|
||||||
|
IGeometryFilter filter) {
|
||||||
|
return createTileGeom(flatFeatureList(g), tileEnvelope, geomFactory,
|
||||||
|
mvtLayerParams, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create geometry clipped and then converted to MVT 'extent' coordinates. Result
|
||||||
|
* contains both clipped geometry (intersection) and transformed geometry for encoding to MVT.
|
||||||
|
*
|
||||||
|
* @param g original 'source' geometry, passed through {@link #flatFeatureList(Geometry)}
|
||||||
|
* @param tileEnvelope world coordinate bounds for tile
|
||||||
|
* @param geomFactory creates a geometry for the tile envelope
|
||||||
|
* @param mvtLayerParams specifies vector tile properties
|
||||||
|
* @param filter geometry values that fail filter after transforms are removed
|
||||||
|
* @return tile geometry result
|
||||||
|
* @see TileGeomResult
|
||||||
|
*/
|
||||||
|
public static TileGeomResult createTileGeom(List<Geometry> g,
|
||||||
|
Envelope tileEnvelope,
|
||||||
|
GeometryFactory geomFactory,
|
||||||
|
MvtLayerParams mvtLayerParams,
|
||||||
|
IGeometryFilter filter) {
|
||||||
|
|
||||||
|
final Geometry tileEnvelopeGeom = geomFactory.toGeometry(tileEnvelope);
|
||||||
|
|
||||||
|
final AffineTransformation t = new AffineTransformation();
|
||||||
|
final double xDiff = tileEnvelope.getWidth();
|
||||||
|
final double yDiff = tileEnvelope.getHeight();
|
||||||
|
|
||||||
|
final double xOffset = -tileEnvelope.getMinX();
|
||||||
|
final double yOffset = -tileEnvelope.getMinY();
|
||||||
|
|
||||||
|
// Transform Setup: Shift to 0 as minimum value
|
||||||
|
t.translate(xOffset, yOffset);
|
||||||
|
|
||||||
|
// Transform Setup: Scale X and Y to tile extent values, flip Y values
|
||||||
|
t.scale(1d / (xDiff / (double) mvtLayerParams.extent),
|
||||||
|
-1d / (yDiff / (double) mvtLayerParams.extent));
|
||||||
|
|
||||||
|
// Transform Setup: Bump Y values to positive quadrant
|
||||||
|
t.translate(0d, (double) mvtLayerParams.extent);
|
||||||
|
|
||||||
|
|
||||||
|
// The area contained in BOTH the 'original geometry', g, AND the 'tile envelope geometry' is the 'tile geometry'
|
||||||
|
final List<Geometry> intersectedGeoms = flatIntersection(tileEnvelopeGeom, g);
|
||||||
|
final List<Geometry> transformedGeoms = new ArrayList<>(intersectedGeoms.size());
|
||||||
|
|
||||||
|
// Transform intersected geometry
|
||||||
|
Geometry nextTransformGeom;
|
||||||
|
Object nextUserData;
|
||||||
|
for(Geometry nextInterGeom : intersectedGeoms) {
|
||||||
|
nextUserData = nextInterGeom.getUserData();
|
||||||
|
|
||||||
|
nextTransformGeom = t.transform(nextInterGeom);
|
||||||
|
|
||||||
|
// Floating --> Integer, still contained within doubles
|
||||||
|
nextTransformGeom.apply(RoundingFilter.INSTANCE);
|
||||||
|
|
||||||
|
// TODO: Refactor line simplification
|
||||||
|
nextTransformGeom = TopologyPreservingSimplifier.simplify(nextTransformGeom, .1d); // Can't use 0d, specify value < .5d
|
||||||
|
|
||||||
|
nextTransformGeom.setUserData(nextUserData);
|
||||||
|
|
||||||
|
// Apply filter on transformed geometry
|
||||||
|
if(filter.accept(nextTransformGeom)) {
|
||||||
|
transformedGeoms.add(nextTransformGeom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TileGeomResult(intersectedGeoms, transformedGeoms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param envelope non-list geometry defines bounding area
|
||||||
|
* @param data geometry passed to {@link #flatFeatureList(Geometry)}
|
||||||
|
* @return list of geometry from {@code data} intersecting with {@code envelope}.
|
||||||
|
* @see #flatIntersection(Geometry, List)
|
||||||
|
*/
|
||||||
|
private static List<Geometry> flatIntersection(Geometry envelope, Geometry data) {
|
||||||
|
return flatIntersection(envelope, flatFeatureList(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JTS 1.14 does not support intersection on a {@link GeometryCollection}. This function works around this
|
||||||
|
* by performing intersection on a flat list of geometry. The resulting list is pre-filtered for invalid
|
||||||
|
* or empty geometry (outside of bounds). Invalid geometry are logged as errors.
|
||||||
|
*
|
||||||
|
* @param envelope non-list geometry defines bounding area
|
||||||
|
* @param dataGeoms geometry pre-passed through {@link #flatFeatureList(Geometry)}
|
||||||
|
* @return list of geometry from {@code data} intersecting with {@code envelope}.
|
||||||
|
*/
|
||||||
|
private static List<Geometry> flatIntersection(Geometry envelope, List<Geometry> dataGeoms) {
|
||||||
|
final List<Geometry> intersectedGeoms = new ArrayList<>(dataGeoms.size());
|
||||||
|
|
||||||
|
Geometry nextIntersected;
|
||||||
|
for(Geometry nextGeom : dataGeoms) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// AABB intersection culling
|
||||||
|
if(envelope.getEnvelopeInternal().intersects(nextGeom.getEnvelopeInternal())) {
|
||||||
|
|
||||||
|
nextIntersected = envelope.intersection(nextGeom);
|
||||||
|
if(!nextIntersected.isEmpty()) {
|
||||||
|
nextIntersected.setUserData(nextGeom.getUserData());
|
||||||
|
intersectedGeoms.add(nextIntersected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (TopologyException e) {
|
||||||
|
//LoggerFactory.getLogger(JtsAdapter.class).error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intersectedGeoms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the MVT type mapping for the provided JTS Geometry.
|
||||||
|
*
|
||||||
|
* @param geometry JTS Geometry to get MVT type for
|
||||||
|
* @return MVT type for the given JTS Geometry, may return
|
||||||
|
* {@link com.wdtinc.mapbox_vector_tile.VectorTile.Tile.GeomType#UNKNOWN}
|
||||||
|
*/
|
||||||
|
public static VectorTile.Tile.GeomType toGeomType(Geometry geometry) {
|
||||||
|
VectorTile.Tile.GeomType result = VectorTile.Tile.GeomType.UNKNOWN;
|
||||||
|
|
||||||
|
if(geometry instanceof Point
|
||||||
|
|| geometry instanceof MultiPoint) {
|
||||||
|
result = VectorTile.Tile.GeomType.POINT;
|
||||||
|
|
||||||
|
} else if(geometry instanceof LineString
|
||||||
|
|| geometry instanceof MultiLineString) {
|
||||||
|
result = VectorTile.Tile.GeomType.LINESTRING;
|
||||||
|
|
||||||
|
} else if(geometry instanceof Polygon
|
||||||
|
|| geometry instanceof MultiPolygon) {
|
||||||
|
result = VectorTile.Tile.GeomType.POLYGON;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Recursively convert a {@link Geometry}, which may be an instance of {@link GeometryCollection} with mixed
|
||||||
|
* element types, into a flat list containing only the following {@link Geometry} types:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link Point}</li>
|
||||||
|
* <li>{@link LineString}</li>
|
||||||
|
* <li>{@link Polygon}</li>
|
||||||
|
* <li>{@link MultiPoint}</li>
|
||||||
|
* <li>{@link MultiLineString}</li>
|
||||||
|
* <li>{@link MultiPolygon}</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>WARNING: Any other Geometry types that were not mentioned in the list above will be discarded!</p>
|
||||||
|
* <p>Useful for converting a generic geometry into a list of simple MVT-feature-ready geometries.</p>
|
||||||
|
*
|
||||||
|
* @param geom geometry to flatten
|
||||||
|
* @return list of MVT-feature-ready geometries
|
||||||
|
*/
|
||||||
|
public static List<Geometry> flatFeatureList(Geometry geom) {
|
||||||
|
final List<Geometry> singleGeoms = new ArrayList<>();
|
||||||
|
final Stack<Geometry> geomStack = new Stack<>();
|
||||||
|
|
||||||
|
Geometry nextGeom;
|
||||||
|
int nextGeomCount;
|
||||||
|
|
||||||
|
geomStack.push(geom);
|
||||||
|
while(!geomStack.isEmpty()) {
|
||||||
|
nextGeom = geomStack.pop();
|
||||||
|
|
||||||
|
if(nextGeom instanceof Point
|
||||||
|
|| nextGeom instanceof MultiPoint
|
||||||
|
|| nextGeom instanceof LineString
|
||||||
|
|| nextGeom instanceof MultiLineString
|
||||||
|
|| nextGeom instanceof Polygon
|
||||||
|
|| nextGeom instanceof MultiPolygon) {
|
||||||
|
|
||||||
|
singleGeoms.add(nextGeom);
|
||||||
|
|
||||||
|
} else if(nextGeom instanceof GeometryCollection) {
|
||||||
|
|
||||||
|
// Push all child geometries
|
||||||
|
nextGeomCount = nextGeom.getNumGeometries();
|
||||||
|
for(int i = 0; i < nextGeomCount; ++i) {
|
||||||
|
geomStack.push(nextGeom.getGeometryN(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return singleGeoms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Convert JTS {@link Geometry} to a list of vector tile features.
|
||||||
|
* The Geometry should be in MVT coordinates.</p>
|
||||||
|
*
|
||||||
|
* <p>Each geometry will have its own ID.</p>
|
||||||
|
*
|
||||||
|
* @param geometry JTS geometry to convert
|
||||||
|
* @param layerProps layer properties for tagging features
|
||||||
|
* @param userDataConverter convert {@link Geometry#userData} to MVT feature tags
|
||||||
|
* @see #flatFeatureList(Geometry)
|
||||||
|
* @see #createTileGeom(Geometry, Envelope, GeometryFactory, MvtLayerParams, IGeometryFilter)
|
||||||
|
*/
|
||||||
|
public static List<VectorTile.Tile.Feature> toFeatures(Geometry geometry,
|
||||||
|
MvtLayerProps layerProps,
|
||||||
|
IUserDataConverter userDataConverter) {
|
||||||
|
return toFeatures(flatFeatureList(geometry), layerProps, userDataConverter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Convert a flat list of JTS {@link Geometry} to a list of vector tile features.
|
||||||
|
* The Geometry should be in MVT coordinates.</p>
|
||||||
|
*
|
||||||
|
* <p>Each geometry will have its own ID.</p>
|
||||||
|
*
|
||||||
|
* @param flatGeoms flat list of JTS geometry to convert
|
||||||
|
* @param layerProps layer properties for tagging features
|
||||||
|
* @param userDataConverter convert {@link Geometry#userData} to MVT feature tags
|
||||||
|
* @see #flatFeatureList(Geometry)
|
||||||
|
* @see #createTileGeom(Geometry, Envelope, GeometryFactory, MvtLayerParams, IGeometryFilter)
|
||||||
|
*/
|
||||||
|
public static List<VectorTile.Tile.Feature> toFeatures(Collection<Geometry> flatGeoms,
|
||||||
|
MvtLayerProps layerProps,
|
||||||
|
IUserDataConverter userDataConverter) {
|
||||||
|
|
||||||
|
// Guard: empty geometry
|
||||||
|
if(flatGeoms.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<VectorTile.Tile.Feature> features = new ArrayList<>();
|
||||||
|
final Vec2d cursor = new Vec2d();
|
||||||
|
|
||||||
|
VectorTile.Tile.Feature nextFeature;
|
||||||
|
|
||||||
|
for(Geometry nextGeom : flatGeoms) {
|
||||||
|
cursor.set(0d, 0d);
|
||||||
|
nextFeature = toFeature(nextGeom, cursor, layerProps, userDataConverter);
|
||||||
|
if(nextFeature != null) {
|
||||||
|
features.add(nextFeature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and return a feature from a geometry. Returns null on failure.
|
||||||
|
*
|
||||||
|
* @param geom flat geometry via {@link #flatFeatureList(Geometry)} that can be translated to a feature
|
||||||
|
* @param cursor vector tile cursor position
|
||||||
|
* @param layerProps layer properties for tagging features
|
||||||
|
* @return new tile feature instance, or null on failure
|
||||||
|
*/
|
||||||
|
private static VectorTile.Tile.Feature toFeature(Geometry geom,
|
||||||
|
Vec2d cursor,
|
||||||
|
MvtLayerProps layerProps,
|
||||||
|
IUserDataConverter userDataConverter) {
|
||||||
|
|
||||||
|
// Guard: UNKNOWN Geometry
|
||||||
|
final VectorTile.Tile.GeomType mvtGeomType = JtsAdapter.toGeomType(geom);
|
||||||
|
if(mvtGeomType == VectorTile.Tile.GeomType.UNKNOWN) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final VectorTile.Tile.Feature.Builder featureBuilder = VectorTile.Tile.Feature.newBuilder();
|
||||||
|
final boolean mvtClosePath = MvtUtil.shouldClosePath(mvtGeomType);
|
||||||
|
final List<Integer> mvtGeom = new ArrayList<>();
|
||||||
|
|
||||||
|
featureBuilder.setType(mvtGeomType);
|
||||||
|
|
||||||
|
if(geom instanceof Point || geom instanceof MultiPoint) {
|
||||||
|
|
||||||
|
// Encode as MVT point or multipoint
|
||||||
|
mvtGeom.addAll(ptsToGeomCmds(geom, cursor));
|
||||||
|
|
||||||
|
} else if(geom instanceof LineString || geom instanceof MultiLineString) {
|
||||||
|
|
||||||
|
// Encode as MVT linestring or multi-linestring
|
||||||
|
for (int i = 0; i < geom.getNumGeometries(); ++i) {
|
||||||
|
mvtGeom.addAll(linesToGeomCmds(geom.getGeometryN(i), mvtClosePath, cursor, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(geom instanceof MultiPolygon || geom instanceof Polygon) {
|
||||||
|
|
||||||
|
// Encode as MVT polygon or multi-polygon
|
||||||
|
for(int i = 0; i < geom.getNumGeometries(); ++i) {
|
||||||
|
|
||||||
|
final Polygon nextPoly = (Polygon) geom.getGeometryN(i);
|
||||||
|
final List<Integer> nextPolyGeom = new ArrayList<>();
|
||||||
|
boolean valid = true;
|
||||||
|
|
||||||
|
// Add exterior ring
|
||||||
|
final LineString exteriorRing = nextPoly.getExteriorRing();
|
||||||
|
|
||||||
|
// Area must be non-zero
|
||||||
|
final double exteriorArea = CGAlgorithms.signedArea(exteriorRing.getCoordinates());
|
||||||
|
if(((int) Math.round(exteriorArea)) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check CCW Winding (must be positive area)
|
||||||
|
if(exteriorArea < 0d) {
|
||||||
|
CoordinateArrays.reverse(exteriorRing.getCoordinates());
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPolyGeom.addAll(linesToGeomCmds(exteriorRing, mvtClosePath, cursor, 2));
|
||||||
|
|
||||||
|
|
||||||
|
// Add interior rings
|
||||||
|
for(int ringIndex = 0; ringIndex < nextPoly.getNumInteriorRing(); ++ringIndex) {
|
||||||
|
|
||||||
|
final LineString nextInteriorRing = nextPoly.getInteriorRingN(ringIndex);
|
||||||
|
|
||||||
|
// Area must be non-zero
|
||||||
|
final double interiorArea = CGAlgorithms.signedArea(nextInteriorRing.getCoordinates());
|
||||||
|
if(((int)Math.round(interiorArea)) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check CW Winding (must be negative area)
|
||||||
|
if(interiorArea > 0d) {
|
||||||
|
CoordinateArrays.reverse(nextInteriorRing.getCoordinates());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interior ring area must be < exterior ring area, or entire geometry is invalid
|
||||||
|
if(Math.abs(exteriorArea) <= Math.abs(interiorArea)) {
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPolyGeom.addAll(linesToGeomCmds(nextInteriorRing, mvtClosePath, cursor, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(valid) {
|
||||||
|
mvtGeom.addAll(nextPolyGeom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(mvtGeom.size() < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
featureBuilder.addAllGeometry(mvtGeom);
|
||||||
|
|
||||||
|
|
||||||
|
// Feature Properties
|
||||||
|
userDataConverter.addTags(geom.getUserData(), layerProps, featureBuilder);
|
||||||
|
|
||||||
|
return featureBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Convert a {@link Point} or {@link MultiPoint} geometry to a list of MVT geometry drawing commands. See
|
||||||
|
* <a href="https://github.com/mapbox/vector-tile-spec">vector-tile-spec</a>
|
||||||
|
* for details.</p>
|
||||||
|
*
|
||||||
|
* <p>WARNING: The value of the {@code cursor} parameter is modified as a result of calling this method.</p>
|
||||||
|
*
|
||||||
|
* @param geom input of type {@link Point} or {@link MultiPoint}. Type is NOT checked and expected to be correct.
|
||||||
|
* @param cursor modified during processing to contain next MVT cursor position
|
||||||
|
* @return list of commands
|
||||||
|
*/
|
||||||
|
private static List<Integer> ptsToGeomCmds(final Geometry geom, final Vec2d cursor) {
|
||||||
|
|
||||||
|
// Guard: empty geometry coordinates
|
||||||
|
final Coordinate[] geomCoords = geom.getCoordinates();
|
||||||
|
if(geomCoords.length <= 0) {
|
||||||
|
Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Tile commands and parameters */
|
||||||
|
final List<Integer> geomCmds = new ArrayList<>(geomCmdBuffLenPts(geomCoords.length));
|
||||||
|
|
||||||
|
/** Holds next MVT coordinate */
|
||||||
|
final Vec2d mvtPos = new Vec2d();
|
||||||
|
|
||||||
|
/** Length of 'MoveTo' draw command */
|
||||||
|
int moveCmdLen = 0;
|
||||||
|
|
||||||
|
// Insert placeholder for 'MoveTo' command header
|
||||||
|
geomCmds.add(0);
|
||||||
|
|
||||||
|
Coordinate nextCoord;
|
||||||
|
|
||||||
|
for(int i = 0; i < geomCoords.length; ++i) {
|
||||||
|
nextCoord = geomCoords[i];
|
||||||
|
mvtPos.set(nextCoord.x, nextCoord.y);
|
||||||
|
|
||||||
|
// Ignore duplicate MVT points
|
||||||
|
if(i == 0 || !equalAsInts(cursor, mvtPos)) {
|
||||||
|
++moveCmdLen;
|
||||||
|
moveCursor(cursor, geomCmds, mvtPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(moveCmdLen <= GeomCmdHdr.CMD_HDR_LEN_MAX) {
|
||||||
|
|
||||||
|
// Write 'MoveTo' command header to first index
|
||||||
|
geomCmds.set(0, GeomCmdHdr.cmdHdr(GeomCmd.MoveTo, moveCmdLen));
|
||||||
|
|
||||||
|
return geomCmds;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Invalid geometry, need at least 1 'MoveTo' value to make points
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Convert a {@link LineString} or {@link Polygon} to a list of MVT geometry drawing commands.
|
||||||
|
* A {@link MultiLineString} or {@link MultiPolygon} can be encoded by calling this method multiple times.</p>
|
||||||
|
*
|
||||||
|
* <p>See <a href="https://github.com/mapbox/vector-tile-spec">vector-tile-spec</a> for details.</p>
|
||||||
|
*
|
||||||
|
* <p>WARNING: The value of the {@code cursor} parameter is modified as a result of calling this method.</p>
|
||||||
|
*
|
||||||
|
* @param geom input of type {@link LineString} or {@link Polygon}. Type is NOT checked and expected to be correct.
|
||||||
|
* @param closeEnabled whether a 'ClosePath' command should terminate the command list
|
||||||
|
* @param cursor modified during processing to contain next MVT cursor position
|
||||||
|
* @param minLineToLen minimum allowed length for LineTo command.
|
||||||
|
* @return list of commands
|
||||||
|
*/
|
||||||
|
private static List<Integer> linesToGeomCmds(
|
||||||
|
final Geometry geom,
|
||||||
|
final boolean closeEnabled,
|
||||||
|
final Vec2d cursor,
|
||||||
|
final int minLineToLen) {
|
||||||
|
|
||||||
|
final Coordinate[] geomCoords = geom.getCoordinates();
|
||||||
|
|
||||||
|
// Check geometry for repeated end points
|
||||||
|
final int repeatEndCoordCount = countCoordRepeatReverse(geomCoords);
|
||||||
|
final int minExpGeomCoords = geomCoords.length - repeatEndCoordCount;
|
||||||
|
|
||||||
|
// Guard/Optimization: Not enough geometry coordinates for a line
|
||||||
|
if(minExpGeomCoords < 2) {
|
||||||
|
Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Tile commands and parameters */
|
||||||
|
final List<Integer> geomCmds = new ArrayList<>(geomCmdBuffLenLines(minExpGeomCoords, closeEnabled));
|
||||||
|
|
||||||
|
/** Holds next MVT coordinate */
|
||||||
|
final Vec2d mvtPos = new Vec2d();
|
||||||
|
|
||||||
|
// Initial coordinate
|
||||||
|
Coordinate nextCoord = geomCoords[0];
|
||||||
|
mvtPos.set(nextCoord.x, nextCoord.y);
|
||||||
|
|
||||||
|
// Encode initial 'MoveTo' command
|
||||||
|
geomCmds.add(GeomCmdHdr.cmdHdr(GeomCmd.MoveTo, 1));
|
||||||
|
|
||||||
|
moveCursor(cursor, geomCmds, mvtPos);
|
||||||
|
|
||||||
|
|
||||||
|
/** Index of 'LineTo' 'command header' */
|
||||||
|
final int lineToCmdHdrIndex = geomCmds.size();
|
||||||
|
|
||||||
|
// Insert placeholder for 'LineTo' command header
|
||||||
|
geomCmds.add(0);
|
||||||
|
|
||||||
|
|
||||||
|
/** Length of 'LineTo' draw command */
|
||||||
|
int lineToLength = 0;
|
||||||
|
|
||||||
|
for(int i = 1; i < minExpGeomCoords; ++i) {
|
||||||
|
nextCoord = geomCoords[i];
|
||||||
|
mvtPos.set(nextCoord.x, nextCoord.y);
|
||||||
|
|
||||||
|
// Ignore duplicate MVT points in sequence
|
||||||
|
if(!equalAsInts(cursor, mvtPos)) {
|
||||||
|
++lineToLength;
|
||||||
|
moveCursor(cursor, geomCmds, mvtPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lineToLength >= minLineToLen && lineToLength <= GeomCmdHdr.CMD_HDR_LEN_MAX) {
|
||||||
|
|
||||||
|
// Write 'LineTo' 'command header'
|
||||||
|
geomCmds.set(lineToCmdHdrIndex, GeomCmdHdr.cmdHdr(GeomCmd.LineTo, lineToLength));
|
||||||
|
|
||||||
|
if(closeEnabled) {
|
||||||
|
geomCmds.add(GeomCmdHdr.closePathCmdHdr());
|
||||||
|
}
|
||||||
|
|
||||||
|
return geomCmds;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Invalid geometry, need at least 1 'LineTo' value to make a Multiline or Polygon
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Count number of coordinates starting from the end of the coordinate array backwards
|
||||||
|
* that match the first coordinate value.</p>
|
||||||
|
*
|
||||||
|
* <p>Useful for ensuring self-closing line strings do not repeat the first coordinate.</p>
|
||||||
|
*
|
||||||
|
* @param coords coordinates to check for duplicate points
|
||||||
|
* @return number of duplicate points at the rear of the list
|
||||||
|
*/
|
||||||
|
private static int countCoordRepeatReverse(Coordinate[] coords) {
|
||||||
|
int repeatCoords = 0;
|
||||||
|
|
||||||
|
final Coordinate firstCoord = coords[0];
|
||||||
|
Coordinate nextCoord;
|
||||||
|
|
||||||
|
for(int i = coords.length - 1; i > 0; --i) {
|
||||||
|
nextCoord = coords[i];
|
||||||
|
if(equalAsInts2d(firstCoord, nextCoord)) {
|
||||||
|
++repeatCoords;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return repeatCoords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Appends {@link ZigZag#encode(int)} of delta in x,y from {@code cursor} to {@code mvtPos} into the {@code geomCmds} buffer.</p>
|
||||||
|
*
|
||||||
|
* <p>Afterwards, the {@code cursor} values are changed to match the {@code mvtPos} values.</p>
|
||||||
|
*
|
||||||
|
* @param cursor MVT cursor position
|
||||||
|
* @param geomCmds geometry command list
|
||||||
|
* @param mvtPos next MVT cursor position
|
||||||
|
*/
|
||||||
|
private static void moveCursor(Vec2d cursor, List<Integer> geomCmds, Vec2d mvtPos) {
|
||||||
|
|
||||||
|
// Delta, then zigzag
|
||||||
|
geomCmds.add(ZigZag.encode((int)mvtPos.x - (int)cursor.x));
|
||||||
|
geomCmds.add(ZigZag.encode((int)mvtPos.y - (int)cursor.y));
|
||||||
|
|
||||||
|
cursor.set(mvtPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the values of the two {@link Coordinate} are equal when their
|
||||||
|
* first and second ordinates are cast as ints. Ignores 3rd ordinate.
|
||||||
|
*
|
||||||
|
* @param a first coordinate to compare
|
||||||
|
* @param b second coordinate to compare
|
||||||
|
* @return true if the values of the two {@link Coordinate} are equal when their
|
||||||
|
* first and second ordinates are cast as ints
|
||||||
|
*/
|
||||||
|
private static boolean equalAsInts2d(Coordinate a, Coordinate b) {
|
||||||
|
return ((int)a.getOrdinate(0)) == ((int)b.getOrdinate(0))
|
||||||
|
&& ((int)a.getOrdinate(1)) == ((int)b.getOrdinate(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the values of the two vectors are equal when cast as ints.
|
||||||
|
*
|
||||||
|
* @param a first vector to compare
|
||||||
|
* @param b second vector to compare
|
||||||
|
* @return true if the values of the two vectors are equal when cast as ints
|
||||||
|
*/
|
||||||
|
private static boolean equalAsInts(Vec2d a, Vec2d b) {
|
||||||
|
return ((int) a.x) == ((int) b.x) && ((int) a.y) == ((int) b.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get required geometry buffer size for a {@link Point} or {@link MultiPoint} geometry.
|
||||||
|
*
|
||||||
|
* @param coordCount coordinate count for the geometry
|
||||||
|
* @return required geometry buffer length
|
||||||
|
*/
|
||||||
|
private static int geomCmdBuffLenPts(int coordCount) {
|
||||||
|
|
||||||
|
// 1 MoveTo Header, 2 parameters * coordCount
|
||||||
|
return 1 + (coordCount * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get required geometry buffer size for a {@link LineString} or {@link Polygon} geometry.
|
||||||
|
*
|
||||||
|
* @param coordCount coordinate count for the geometry
|
||||||
|
* @param closeEnabled whether a 'ClosePath' command should terminate the command list
|
||||||
|
* @return required geometry buffer length
|
||||||
|
*/
|
||||||
|
private static int geomCmdBuffLenLines(int coordCount, boolean closeEnabled) {
|
||||||
|
|
||||||
|
// MoveTo Header, LineTo Header, Optional ClosePath Header, 2 parameters * coordCount
|
||||||
|
return 2 + (closeEnabled ? 1 : 0) + (coordCount * 2);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,599 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.adapt.jts;
|
||||||
|
|
||||||
|
import com.vividsolutions.jts.algorithm.CGAlgorithms;
|
||||||
|
import com.vividsolutions.jts.geom.Coordinate;
|
||||||
|
import com.vividsolutions.jts.geom.CoordinateSequence;
|
||||||
|
import com.vividsolutions.jts.geom.Geometry;
|
||||||
|
import com.vividsolutions.jts.geom.GeometryFactory;
|
||||||
|
import com.vividsolutions.jts.geom.LineString;
|
||||||
|
import com.vividsolutions.jts.geom.LinearRing;
|
||||||
|
import com.vividsolutions.jts.geom.MultiLineString;
|
||||||
|
import com.vividsolutions.jts.geom.MultiPoint;
|
||||||
|
import com.vividsolutions.jts.geom.MultiPolygon;
|
||||||
|
import com.vividsolutions.jts.geom.Point;
|
||||||
|
import com.vividsolutions.jts.geom.Polygon;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.encoding.GeomCmd;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.encoding.GeomCmdHdr;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.encoding.ZigZag;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.util.Vec2d;
|
||||||
|
|
||||||
|
import net.osmand.binary.VectorTile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Mapbox Vector Tiles (MVT) to JTS {@link Geometry}. Feature tags may be converted
|
||||||
|
* to user data via {@link ITagConverter}.
|
||||||
|
*/
|
||||||
|
public final class MvtReader {
|
||||||
|
private static final int MIN_LINE_STRING_LEN = 6; // MoveTo,1 + LineTo,1
|
||||||
|
private static final int MIN_POLYGON_LEN = 9; // MoveTo,1 + LineTo,2 + ClosePath
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for loading MVT from file.
|
||||||
|
* See {@link #loadMvt(InputStream, GeometryFactory, ITagConverter, RingClassifier)}.
|
||||||
|
* Uses {@link #RING_CLASSIFIER_V2_1} for forming Polygons and MultiPolygons.
|
||||||
|
*
|
||||||
|
* @param f MVT file
|
||||||
|
* @param geomFactory allows for JTS geometry creation
|
||||||
|
* @param tagConverter converts MVT feature tags to JTS user data object
|
||||||
|
* @return JTS geometries in using MVT coordinates
|
||||||
|
* @throws IOException failure reading MVT from path
|
||||||
|
* @see #loadMvt(InputStream, GeometryFactory, ITagConverter, RingClassifier)
|
||||||
|
* @see Geometry
|
||||||
|
* @see Geometry#getUserData()
|
||||||
|
* @see RingClassifier
|
||||||
|
*/
|
||||||
|
public static List<Geometry> loadMvt(File f,
|
||||||
|
GeometryFactory geomFactory,
|
||||||
|
ITagConverter tagConverter) throws IOException {
|
||||||
|
return loadMvt(f, geomFactory, tagConverter, RING_CLASSIFIER_V2_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for loading MVT from file.
|
||||||
|
* See {@link #loadMvt(InputStream, GeometryFactory, ITagConverter, RingClassifier)}.
|
||||||
|
*
|
||||||
|
* @param f MVT file
|
||||||
|
* @param geomFactory allows for JTS geometry creation
|
||||||
|
* @param tagConverter converts MVT feature tags to JTS user data object
|
||||||
|
* @param ringClassifier determines how rings are parsed into Polygons and MultiPolygons
|
||||||
|
* @return JTS geometries in using MVT coordinates
|
||||||
|
* @throws IOException failure reading MVT from path
|
||||||
|
* @see #loadMvt(InputStream, GeometryFactory, ITagConverter, RingClassifier)
|
||||||
|
* @see Geometry
|
||||||
|
* @see Geometry#getUserData()
|
||||||
|
* @see RingClassifier
|
||||||
|
*/
|
||||||
|
public static List<Geometry> loadMvt(File f,
|
||||||
|
GeometryFactory geomFactory,
|
||||||
|
ITagConverter tagConverter,
|
||||||
|
RingClassifier ringClassifier) throws IOException {
|
||||||
|
final InputStream is = new FileInputStream(f);
|
||||||
|
return loadMvt(is, geomFactory, tagConverter, ringClassifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load an MVT to JTS geometries using coordinates. Uses {@code tagConverter} to create user data
|
||||||
|
* from feature properties. Uses {@link #RING_CLASSIFIER_V2_1} for forming Polygons and MultiPolygons.
|
||||||
|
*
|
||||||
|
* @param is stream with MVT data
|
||||||
|
* @param geomFactory allows for JTS geometry creation
|
||||||
|
* @param tagConverter converts MVT feature tags to JTS user data object.
|
||||||
|
* @return JTS geometries in using MVT coordinates
|
||||||
|
* @throws IOException failure reading MVT from stream
|
||||||
|
* @see Geometry
|
||||||
|
* @see Geometry#getUserData()
|
||||||
|
* @see RingClassifier
|
||||||
|
*/
|
||||||
|
public static List<Geometry> loadMvt(InputStream is,
|
||||||
|
GeometryFactory geomFactory,
|
||||||
|
ITagConverter tagConverter) throws IOException {
|
||||||
|
return loadMvt(is, geomFactory, tagConverter, RING_CLASSIFIER_V2_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load an MVT to JTS geometries using coordinates. Uses {@code tagConverter} to create user data
|
||||||
|
* from feature properties.
|
||||||
|
*
|
||||||
|
* @param is stream with MVT data
|
||||||
|
* @param geomFactory allows for JTS geometry creation
|
||||||
|
* @param tagConverter converts MVT feature tags to JTS user data object.
|
||||||
|
* @param ringClassifier determines how rings are parsed into Polygons and MultiPolygons
|
||||||
|
* @return JTS geometries in using MVT coordinates
|
||||||
|
* @throws IOException failure reading MVT from stream
|
||||||
|
* @see Geometry
|
||||||
|
* @see Geometry#getUserData()
|
||||||
|
* @see RingClassifier
|
||||||
|
*/
|
||||||
|
public static List<Geometry> loadMvt(InputStream is,
|
||||||
|
GeometryFactory geomFactory,
|
||||||
|
ITagConverter tagConverter,
|
||||||
|
RingClassifier ringClassifier) throws IOException {
|
||||||
|
|
||||||
|
final List<Geometry> tileGeoms = new ArrayList<>();
|
||||||
|
final VectorTile.Tile mvt = VectorTile.Tile.parseFrom(is);
|
||||||
|
final Vec2d cursor = new Vec2d();
|
||||||
|
|
||||||
|
for (VectorTile.Tile.Layer nextLayer : mvt.getLayersList()) {
|
||||||
|
|
||||||
|
final List<String> keysList = nextLayer.getKeysList();
|
||||||
|
final List<VectorTile.Tile.Value> valuesList = nextLayer.getValuesList();
|
||||||
|
|
||||||
|
for (VectorTile.Tile.Feature nextFeature : nextLayer.getFeaturesList()) {
|
||||||
|
|
||||||
|
final Long id = nextFeature.hasId() ? nextFeature.getId() : null;
|
||||||
|
|
||||||
|
final VectorTile.Tile.GeomType geomType = nextFeature.getType();
|
||||||
|
|
||||||
|
if (geomType == VectorTile.Tile.GeomType.UNKNOWN) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Integer> geomCmds = nextFeature.getGeometryList();
|
||||||
|
cursor.set(0d, 0d);
|
||||||
|
final Geometry nextGeom = readGeometry(geomCmds, geomType, geomFactory, cursor, ringClassifier);
|
||||||
|
if (nextGeom != null) {
|
||||||
|
tileGeoms.add(nextGeom);
|
||||||
|
nextGeom.setUserData(tagConverter.toUserData(id, nextFeature.getTagsList(), keysList, valuesList));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tileGeoms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Geometry readGeometry(List<Integer> geomCmds,
|
||||||
|
VectorTile.Tile.GeomType geomType,
|
||||||
|
GeometryFactory geomFactory,
|
||||||
|
Vec2d cursor,
|
||||||
|
RingClassifier ringClassifier) {
|
||||||
|
Geometry result = null;
|
||||||
|
|
||||||
|
switch (geomType) {
|
||||||
|
case POINT:
|
||||||
|
result = readPoints(geomFactory, geomCmds, cursor);
|
||||||
|
break;
|
||||||
|
case LINESTRING:
|
||||||
|
result = readLines(geomFactory, geomCmds, cursor);
|
||||||
|
break;
|
||||||
|
case POLYGON:
|
||||||
|
result = readPolys(geomFactory, geomCmds, cursor, ringClassifier);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
//LoggerFactory.getLogger(MvtReader.class).error("readGeometry(): Unhandled geometry type [{}]", geomType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create {@link Point} or {@link MultiPoint} from MVT geometry drawing commands.
|
||||||
|
*
|
||||||
|
* @param geomFactory creates JTS geometry
|
||||||
|
* @param geomCmds contains MVT geometry commands
|
||||||
|
* @param cursor contains current MVT extent position
|
||||||
|
* @return JTS geometry or null on failure
|
||||||
|
*/
|
||||||
|
private static Geometry readPoints(GeometryFactory geomFactory, List<Integer> geomCmds, Vec2d cursor) {
|
||||||
|
|
||||||
|
// Guard: must have header
|
||||||
|
if (geomCmds.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Geometry command index */
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
// Read command header
|
||||||
|
final int cmdHdr = geomCmds.get(i++);
|
||||||
|
final int cmdLength = GeomCmdHdr.getCmdLength(cmdHdr);
|
||||||
|
final GeomCmd cmd = GeomCmdHdr.getCmd(cmdHdr);
|
||||||
|
|
||||||
|
// Guard: command type
|
||||||
|
if (cmd != GeomCmd.MoveTo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guard: minimum command length
|
||||||
|
if (cmdLength < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guard: header data unsupported by geometry command buffer
|
||||||
|
// (require header and at least 1 value * 2 params)
|
||||||
|
if (cmdLength * GeomCmd.MoveTo.getParamCount() + 1 > geomCmds.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CoordinateSequence coordSeq = geomFactory.getCoordinateSequenceFactory().create(cmdLength, 2);
|
||||||
|
int coordIndex = 0;
|
||||||
|
Coordinate nextCoord;
|
||||||
|
|
||||||
|
while (i < geomCmds.size() - 1) {
|
||||||
|
cursor.add(
|
||||||
|
ZigZag.decode(geomCmds.get(i++)),
|
||||||
|
ZigZag.decode(geomCmds.get(i++))
|
||||||
|
);
|
||||||
|
|
||||||
|
nextCoord = coordSeq.getCoordinate(coordIndex++);
|
||||||
|
nextCoord.setOrdinate(0, cursor.x);
|
||||||
|
nextCoord.setOrdinate(1, cursor.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return coordSeq.size() == 1 ? geomFactory.createPoint(coordSeq) : geomFactory.createMultiPoint(coordSeq);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create {@link LineString} or {@link MultiLineString} from MVT geometry drawing commands.
|
||||||
|
*
|
||||||
|
* @param geomFactory creates JTS geometry
|
||||||
|
* @param geomCmds contains MVT geometry commands
|
||||||
|
* @param cursor contains current MVT extent position
|
||||||
|
* @return JTS geometry or null on failure
|
||||||
|
*/
|
||||||
|
private static Geometry readLines(GeometryFactory geomFactory, List<Integer> geomCmds, Vec2d cursor) {
|
||||||
|
|
||||||
|
// Guard: must have header
|
||||||
|
if (geomCmds.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Geometry command index */
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
int cmdHdr;
|
||||||
|
int cmdLength;
|
||||||
|
GeomCmd cmd;
|
||||||
|
List<LineString> geoms = new ArrayList<>(1);
|
||||||
|
CoordinateSequence nextCoordSeq;
|
||||||
|
Coordinate nextCoord;
|
||||||
|
|
||||||
|
while (i <= geomCmds.size() - MIN_LINE_STRING_LEN) {
|
||||||
|
|
||||||
|
// --------------------------------------------
|
||||||
|
// Expected: MoveTo command of length 1
|
||||||
|
// --------------------------------------------
|
||||||
|
|
||||||
|
// Read command header
|
||||||
|
cmdHdr = geomCmds.get(i++);
|
||||||
|
cmdLength = GeomCmdHdr.getCmdLength(cmdHdr);
|
||||||
|
cmd = GeomCmdHdr.getCmd(cmdHdr);
|
||||||
|
|
||||||
|
// Guard: command type and length
|
||||||
|
if (cmd != GeomCmd.MoveTo || cmdLength != 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cursor position with relative move
|
||||||
|
cursor.add(
|
||||||
|
ZigZag.decode(geomCmds.get(i++)),
|
||||||
|
ZigZag.decode(geomCmds.get(i++))
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------
|
||||||
|
// Expected: LineTo command of length > 0
|
||||||
|
// --------------------------------------------
|
||||||
|
|
||||||
|
// Read command header
|
||||||
|
cmdHdr = geomCmds.get(i++);
|
||||||
|
cmdLength = GeomCmdHdr.getCmdLength(cmdHdr);
|
||||||
|
cmd = GeomCmdHdr.getCmd(cmdHdr);
|
||||||
|
|
||||||
|
// Guard: command type and length
|
||||||
|
if (cmd != GeomCmd.LineTo || cmdLength < 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guard: header data length unsupported by geometry command buffer
|
||||||
|
// (require at least (1 value * 2 params) + current_index)
|
||||||
|
if ((cmdLength * GeomCmd.LineTo.getParamCount()) + i > geomCmds.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextCoordSeq = geomFactory.getCoordinateSequenceFactory().create(1 + cmdLength, 2);
|
||||||
|
|
||||||
|
// Set first point from MoveTo command
|
||||||
|
nextCoord = nextCoordSeq.getCoordinate(0);
|
||||||
|
nextCoord.setOrdinate(0, cursor.x);
|
||||||
|
nextCoord.setOrdinate(1, cursor.y);
|
||||||
|
|
||||||
|
// Set remaining points from LineTo command
|
||||||
|
for (int lineToIndex = 0; lineToIndex < cmdLength; ++lineToIndex) {
|
||||||
|
|
||||||
|
// Update cursor position with relative line delta
|
||||||
|
cursor.add(
|
||||||
|
ZigZag.decode(geomCmds.get(i++)),
|
||||||
|
ZigZag.decode(geomCmds.get(i++))
|
||||||
|
);
|
||||||
|
|
||||||
|
nextCoord = nextCoordSeq.getCoordinate(lineToIndex + 1);
|
||||||
|
nextCoord.setOrdinate(0, cursor.x);
|
||||||
|
nextCoord.setOrdinate(1, cursor.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
geoms.add(geomFactory.createLineString(nextCoordSeq));
|
||||||
|
}
|
||||||
|
|
||||||
|
return geoms.size() == 1 ? geoms.get(0) : geomFactory.createMultiLineString(geoms.toArray(new LineString[geoms.size()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create {@link Polygon} or {@link MultiPolygon} from MVT geometry drawing commands.
|
||||||
|
*
|
||||||
|
* @param geomFactory creates JTS geometry
|
||||||
|
* @param geomCmds contains MVT geometry commands
|
||||||
|
* @param cursor contains current MVT extent position
|
||||||
|
* @param ringClassifier
|
||||||
|
* @return JTS geometry or null on failure
|
||||||
|
*/
|
||||||
|
private static Geometry readPolys(GeometryFactory geomFactory,
|
||||||
|
List<Integer> geomCmds,
|
||||||
|
Vec2d cursor,
|
||||||
|
RingClassifier ringClassifier) {
|
||||||
|
|
||||||
|
// Guard: must have header
|
||||||
|
if (geomCmds.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Geometry command index */
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
int cmdHdr;
|
||||||
|
int cmdLength;
|
||||||
|
GeomCmd cmd;
|
||||||
|
List<LinearRing> rings = new ArrayList<>(1);
|
||||||
|
CoordinateSequence nextCoordSeq;
|
||||||
|
Coordinate nextCoord;
|
||||||
|
|
||||||
|
while (i <= geomCmds.size() - MIN_POLYGON_LEN) {
|
||||||
|
|
||||||
|
// --------------------------------------------
|
||||||
|
// Expected: MoveTo command of length 1
|
||||||
|
// --------------------------------------------
|
||||||
|
|
||||||
|
// Read command header
|
||||||
|
cmdHdr = geomCmds.get(i++);
|
||||||
|
cmdLength = GeomCmdHdr.getCmdLength(cmdHdr);
|
||||||
|
cmd = GeomCmdHdr.getCmd(cmdHdr);
|
||||||
|
|
||||||
|
// Guard: command type and length
|
||||||
|
if (cmd != GeomCmd.MoveTo || cmdLength != 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cursor position with relative move
|
||||||
|
cursor.add(
|
||||||
|
ZigZag.decode(geomCmds.get(i++)),
|
||||||
|
ZigZag.decode(geomCmds.get(i++))
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------
|
||||||
|
// Expected: LineTo command of length > 1
|
||||||
|
// --------------------------------------------
|
||||||
|
|
||||||
|
// Read command header
|
||||||
|
cmdHdr = geomCmds.get(i++);
|
||||||
|
cmdLength = GeomCmdHdr.getCmdLength(cmdHdr);
|
||||||
|
cmd = GeomCmdHdr.getCmd(cmdHdr);
|
||||||
|
|
||||||
|
// Guard: command type and length
|
||||||
|
if (cmd != GeomCmd.LineTo || cmdLength < 2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guard: header data length unsupported by geometry command buffer
|
||||||
|
// (require at least (2 values * 2 params) + (current index 'i') + (1 for ClosePath))
|
||||||
|
if ((cmdLength * GeomCmd.LineTo.getParamCount()) + i + 1 > geomCmds.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextCoordSeq = geomFactory.getCoordinateSequenceFactory().create(2 + cmdLength, 2);
|
||||||
|
|
||||||
|
// Set first point from MoveTo command
|
||||||
|
nextCoord = nextCoordSeq.getCoordinate(0);
|
||||||
|
nextCoord.setOrdinate(0, cursor.x);
|
||||||
|
nextCoord.setOrdinate(1, cursor.y);
|
||||||
|
|
||||||
|
// Set remaining points from LineTo command
|
||||||
|
for (int lineToIndex = 0; lineToIndex < cmdLength; ++lineToIndex) {
|
||||||
|
|
||||||
|
// Update cursor position with relative line delta
|
||||||
|
cursor.add(
|
||||||
|
ZigZag.decode(geomCmds.get(i++)),
|
||||||
|
ZigZag.decode(geomCmds.get(i++))
|
||||||
|
);
|
||||||
|
|
||||||
|
nextCoord = nextCoordSeq.getCoordinate(lineToIndex + 1);
|
||||||
|
nextCoord.setOrdinate(0, cursor.x);
|
||||||
|
nextCoord.setOrdinate(1, cursor.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------
|
||||||
|
// Expected: ClosePath command of length 1
|
||||||
|
// --------------------------------------------
|
||||||
|
|
||||||
|
// Read command header
|
||||||
|
cmdHdr = geomCmds.get(i++);
|
||||||
|
cmdLength = GeomCmdHdr.getCmdLength(cmdHdr);
|
||||||
|
cmd = GeomCmdHdr.getCmd(cmdHdr);
|
||||||
|
|
||||||
|
if (cmd != GeomCmd.ClosePath || cmdLength != 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set last point from ClosePath command
|
||||||
|
nextCoord = nextCoordSeq.getCoordinate(nextCoordSeq.size() - 1);
|
||||||
|
nextCoord.setOrdinate(0, nextCoordSeq.getOrdinate(0, 0));
|
||||||
|
nextCoord.setOrdinate(1, nextCoordSeq.getOrdinate(0, 1));
|
||||||
|
|
||||||
|
rings.add(geomFactory.createLinearRing(nextCoordSeq));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Classify rings
|
||||||
|
final List<Polygon> polygons = ringClassifier.classifyRings(rings, geomFactory);
|
||||||
|
if (polygons.size() < 1) {
|
||||||
|
return null;
|
||||||
|
|
||||||
|
} else if (polygons.size() == 1) {
|
||||||
|
return polygons.get(0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return geomFactory.createMultiPolygon(polygons.toArray(new Polygon[polygons.size()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classifies Polygon and MultiPolygon rings.
|
||||||
|
*/
|
||||||
|
public interface RingClassifier {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Classify a list of rings into polygons using surveyor formula.</p>
|
||||||
|
* <p>
|
||||||
|
* <p>Zero-area polygons are removed.</p>
|
||||||
|
*
|
||||||
|
* @param rings linear rings to classify into polygons
|
||||||
|
* @param geomFactory creates JTS geometry
|
||||||
|
* @return polygons from classified rings
|
||||||
|
*/
|
||||||
|
List<Polygon> classifyRings(List<LinearRing> rings, GeometryFactory geomFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Area for surveyor formula may be positive or negative for exterior rings. Mimics Mapbox parsers supporting V1.
|
||||||
|
*/
|
||||||
|
public static final RingClassifier RING_CLASSIFIER_V1 = new PolyRingClassifierV1();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Area from surveyor formula must be positive for exterior rings. Obeys V2.1 spec.
|
||||||
|
*/
|
||||||
|
public static final RingClassifier RING_CLASSIFIER_V2_1 = new PolyRingClassifierV2_1();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Area from surveyor formula must be positive for exterior rings. Obeys V2.1 spec.
|
||||||
|
*
|
||||||
|
* @see CGAlgorithms#signedArea(Coordinate[])
|
||||||
|
*/
|
||||||
|
private static final class PolyRingClassifierV2_1 implements RingClassifier {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Polygon> classifyRings(List<LinearRing> rings, GeometryFactory geomFactory) {
|
||||||
|
final List<Polygon> polygons = new ArrayList<>();
|
||||||
|
final List<LinearRing> holes = new ArrayList<>();
|
||||||
|
|
||||||
|
double outerArea = 0d;
|
||||||
|
LinearRing outerPoly = null;
|
||||||
|
|
||||||
|
for (LinearRing r : rings) {
|
||||||
|
double area = CGAlgorithms.signedArea(r.getCoordinates());
|
||||||
|
|
||||||
|
if (!r.isRing()) {
|
||||||
|
continue; // sanity check, could probably be handled in a isSimple() check
|
||||||
|
}
|
||||||
|
|
||||||
|
if (area == 0d) {
|
||||||
|
continue; // zero-area
|
||||||
|
}
|
||||||
|
|
||||||
|
if (area > 0d) {
|
||||||
|
if (outerPoly != null) {
|
||||||
|
polygons.add(geomFactory.createPolygon(outerPoly, holes.toArray(new LinearRing[holes.size()])));
|
||||||
|
holes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pos --> CCW, Outer
|
||||||
|
outerPoly = r;
|
||||||
|
outerArea = area;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (Math.abs(outerArea) < Math.abs(area)) {
|
||||||
|
continue; // Holes must have less area, could probably be handled in a isSimple() check
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neg --> CW, Hole
|
||||||
|
holes.add(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outerPoly != null) {
|
||||||
|
holes.toArray();
|
||||||
|
polygons.add(geomFactory.createPolygon(outerPoly, holes.toArray(new LinearRing[holes.size()])));
|
||||||
|
}
|
||||||
|
|
||||||
|
return polygons;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Area for surveyor formula may be positive or negative for exterior rings. Mimics Mapbox parsers supporting V1.
|
||||||
|
*
|
||||||
|
* @see CGAlgorithms#signedArea(Coordinate[])
|
||||||
|
*/
|
||||||
|
private static final class PolyRingClassifierV1 implements RingClassifier {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Polygon> classifyRings(List<LinearRing> rings, GeometryFactory geomFactory) {
|
||||||
|
final List<Polygon> polygons = new ArrayList<>();
|
||||||
|
final List<LinearRing> holes = new ArrayList<>();
|
||||||
|
|
||||||
|
double outerArea = 0d;
|
||||||
|
LinearRing outerPoly = null;
|
||||||
|
|
||||||
|
for (LinearRing r : rings) {
|
||||||
|
double area = CGAlgorithms.signedArea(r.getCoordinates());
|
||||||
|
|
||||||
|
if (!r.isRing()) {
|
||||||
|
continue; // sanity check, could probably be handled in a isSimple() check
|
||||||
|
}
|
||||||
|
|
||||||
|
if (area == 0d) {
|
||||||
|
continue; // zero-area
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outerPoly == null || (outerArea < 0 == area < 0)) {
|
||||||
|
if (outerPoly != null) {
|
||||||
|
polygons.add(geomFactory.createPolygon(outerPoly, holes.toArray(new LinearRing[holes.size()])));
|
||||||
|
holes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pos --> CCW, Outer
|
||||||
|
outerPoly = r;
|
||||||
|
outerArea = area;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (Math.abs(outerArea) < Math.abs(area)) {
|
||||||
|
continue; // Holes must have less area, could probably be handled in a isSimple() check
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neg --> CW, Hole
|
||||||
|
holes.add(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outerPoly != null) {
|
||||||
|
holes.toArray();
|
||||||
|
polygons.add(geomFactory.createPolygon(outerPoly, holes.toArray(new LinearRing[holes.size()])));
|
||||||
|
}
|
||||||
|
|
||||||
|
return polygons;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.adapt.jts;
|
||||||
|
|
||||||
|
import com.vividsolutions.jts.geom.CoordinateSequence;
|
||||||
|
import com.vividsolutions.jts.geom.CoordinateSequenceFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Round each coordinate value to an integer.</p>
|
||||||
|
*
|
||||||
|
* <p>Mapbox vector tiles have fixed precision. This filter can be useful for reducing precision to
|
||||||
|
* the extent of a MVT.</p>
|
||||||
|
*/
|
||||||
|
public final class RoundingFilter implements CoordinateSequenceFilter {
|
||||||
|
|
||||||
|
public static final RoundingFilter INSTANCE = new RoundingFilter();
|
||||||
|
|
||||||
|
private RoundingFilter() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void filter(CoordinateSequence seq, int i) {
|
||||||
|
seq.setOrdinate(i, 0, Math.round(seq.getOrdinate(i, 0)));
|
||||||
|
seq.setOrdinate(i, 1, Math.round(seq.getOrdinate(i, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDone() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGeometryChanged() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.adapt.jts;
|
||||||
|
|
||||||
|
import net.osmand.binary.VectorTile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignores tags, always returns null.
|
||||||
|
*
|
||||||
|
* @see ITagConverter
|
||||||
|
*/
|
||||||
|
public final class TagIgnoreConverter implements ITagConverter {
|
||||||
|
@Override
|
||||||
|
public Object toUserData(Long id, List<Integer> tags, List<String> keysList,
|
||||||
|
List<VectorTile.Tile.Value> valuesList) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.adapt.jts;
|
||||||
|
|
||||||
|
import com.wdtinc.mapbox_vector_tile.encoding.MvtValue;
|
||||||
|
|
||||||
|
import net.osmand.binary.VectorTile;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert MVT tags list to a {@link Map} of {@link String} to {@link Object}. Tags indices that are out
|
||||||
|
* of range of the key or value list are ignored.
|
||||||
|
*
|
||||||
|
* @see ITagConverter
|
||||||
|
*/
|
||||||
|
public final class TagKeyValueMapConverter implements ITagConverter {
|
||||||
|
|
||||||
|
/** If true, return null user data when tags are empty */
|
||||||
|
private final boolean nullIfEmpty;
|
||||||
|
|
||||||
|
/** If true, add id to user data object */
|
||||||
|
private final boolean addId;
|
||||||
|
|
||||||
|
/** The {@link Map} key for the feature id. */
|
||||||
|
private final String idKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Always created user data object, even with empty tags. Ignore feature ids.
|
||||||
|
*/
|
||||||
|
public TagKeyValueMapConverter() {
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignore feature ids.
|
||||||
|
*
|
||||||
|
* @param nullIfEmpty if true, return null user data when tags are empty
|
||||||
|
*/
|
||||||
|
public TagKeyValueMapConverter(boolean nullIfEmpty) {
|
||||||
|
this.nullIfEmpty = nullIfEmpty;
|
||||||
|
this.addId = false;
|
||||||
|
this.idKey = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store feature ids using idKey. Id value may be null if not present.
|
||||||
|
*
|
||||||
|
* @param nullIfEmpty if true, return null user data when tags are empty
|
||||||
|
* @param idKey key name to use for feature id value
|
||||||
|
*/
|
||||||
|
public TagKeyValueMapConverter(boolean nullIfEmpty, String idKey) {
|
||||||
|
if (idKey == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nullIfEmpty = nullIfEmpty;
|
||||||
|
this.addId = true;
|
||||||
|
this.idKey = idKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object toUserData(Long id, List<Integer> tags, List<String> keysList,
|
||||||
|
List<VectorTile.Tile.Value> valuesList) {
|
||||||
|
|
||||||
|
// Guard: empty
|
||||||
|
if(nullIfEmpty && tags.size() < 1 && (!addId || id == null)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final Map<String, Object> userData = new HashMap<>(((tags.size() + 1) / 2));
|
||||||
|
|
||||||
|
// Add feature properties
|
||||||
|
int keyIndex;
|
||||||
|
int valIndex;
|
||||||
|
boolean valid;
|
||||||
|
|
||||||
|
for(int i = 0; i < tags.size() - 1; i += 2) {
|
||||||
|
keyIndex = tags.get(i);
|
||||||
|
valIndex = tags.get(i + 1);
|
||||||
|
|
||||||
|
valid = keyIndex >= 0 && keyIndex < keysList.size()
|
||||||
|
&& valIndex >= 0 && valIndex < valuesList.size();
|
||||||
|
|
||||||
|
if(valid) {
|
||||||
|
userData.put(keysList.get(keyIndex), MvtValue.toObject(valuesList.get(valIndex)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ID, value may be null
|
||||||
|
if(addId) {
|
||||||
|
userData.put(idKey, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userData;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.adapt.jts;
|
||||||
|
|
||||||
|
import com.vividsolutions.jts.geom.Envelope;
|
||||||
|
import com.vividsolutions.jts.geom.Geometry;
|
||||||
|
import com.vividsolutions.jts.geom.GeometryFactory;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.build.MvtLayerParams;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processing result containing intersection geometry and MVT geometry.
|
||||||
|
*
|
||||||
|
* @see JtsAdapter#createTileGeom(Geometry, Envelope, GeometryFactory, MvtLayerParams, IGeometryFilter)
|
||||||
|
*/
|
||||||
|
public final class TileGeomResult {
|
||||||
|
|
||||||
|
/** Intersection geometry (projection units and coordinates) */
|
||||||
|
public final List<Geometry> intGeoms;
|
||||||
|
|
||||||
|
/** Geometry in MVT coordinates (tile extent units, screen coordinates) */
|
||||||
|
public final List<Geometry> mvtGeoms;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param intGeoms geometry intersecting tile
|
||||||
|
* @param mvtGeoms geometry for MVT
|
||||||
|
* @throws NullPointerException if intGeoms or mvtGeoms are null
|
||||||
|
*/
|
||||||
|
public TileGeomResult(List<Geometry> intGeoms, List<Geometry> mvtGeoms) {
|
||||||
|
if(intGeoms == null || mvtGeoms == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
this.intGeoms = intGeoms;
|
||||||
|
this.mvtGeoms = mvtGeoms;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.adapt.jts;
|
||||||
|
|
||||||
|
import com.wdtinc.mapbox_vector_tile.build.MvtLayerProps;
|
||||||
|
|
||||||
|
import net.osmand.binary.VectorTile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignores user data, does not take any action.
|
||||||
|
*
|
||||||
|
* @see IUserDataConverter
|
||||||
|
*/
|
||||||
|
public final class UserDataIgnoreConverter implements IUserDataConverter {
|
||||||
|
@Override
|
||||||
|
public void addTags(Object userData, MvtLayerProps layerProps, VectorTile.Tile.Feature.Builder featureBuilder) {}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.adapt.jts;
|
||||||
|
|
||||||
|
import com.wdtinc.mapbox_vector_tile.build.MvtLayerProps;
|
||||||
|
|
||||||
|
import net.osmand.binary.VectorTile;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert simple user data {@link Map} where the keys are {@link String} and values are {@link Object}. Supports
|
||||||
|
* converting a specific map key to a user id. If the key to user id conversion fails, the error occurs silently
|
||||||
|
* and the id is discarded.
|
||||||
|
*
|
||||||
|
* @see IUserDataConverter
|
||||||
|
*/
|
||||||
|
public final class UserDataKeyValueMapConverter implements IUserDataConverter {
|
||||||
|
|
||||||
|
/** If true, set feature id from user data */
|
||||||
|
private final boolean setId;
|
||||||
|
|
||||||
|
/** The {@link Map} key for the feature id. */
|
||||||
|
private final String idKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does not set feature id.
|
||||||
|
*/
|
||||||
|
public UserDataKeyValueMapConverter() {
|
||||||
|
this.setId = false;
|
||||||
|
this.idKey = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to set feature id using provided user data {@link Map} key.
|
||||||
|
*
|
||||||
|
* @param idKey user data {@link Map} key for getting id value.
|
||||||
|
*/
|
||||||
|
public UserDataKeyValueMapConverter(String idKey) {
|
||||||
|
if (idKey == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
this.setId = true;
|
||||||
|
this.idKey = idKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTags(Object userData, MvtLayerProps layerProps, VectorTile.Tile.Feature.Builder featureBuilder) {
|
||||||
|
if(userData != null) {
|
||||||
|
try {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Map<String, Object> userDataMap = (Map<String, Object>)userData;
|
||||||
|
|
||||||
|
for(Map.Entry<String, Object> e :userDataMap.entrySet()){
|
||||||
|
final String key = e.getKey();
|
||||||
|
final Object value = e.getValue();
|
||||||
|
|
||||||
|
if(key != null && value != null) {
|
||||||
|
final int valueIndex = layerProps.addValue(value);
|
||||||
|
|
||||||
|
if(valueIndex >= 0) {
|
||||||
|
featureBuilder.addTags(layerProps.addKey(key));
|
||||||
|
featureBuilder.addTags(valueIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set feature id value
|
||||||
|
if(setId) {
|
||||||
|
final Object idValue = userDataMap.get(idKey);
|
||||||
|
if (idValue != null) {
|
||||||
|
if(idValue instanceof Long || idValue instanceof Integer
|
||||||
|
|| idValue instanceof Float || idValue instanceof Double
|
||||||
|
|| idValue instanceof Byte || idValue instanceof Short) {
|
||||||
|
featureBuilder.setId((long)idValue);
|
||||||
|
} else if(idValue instanceof String) {
|
||||||
|
try {
|
||||||
|
featureBuilder.setId(Long.valueOf((String)idValue));
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
//LoggerFactory.getLogger(UserDataKeyValueMapConverter.class).error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.encoding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MVT draw command types.
|
||||||
|
*
|
||||||
|
* @see GeomCmdHdr
|
||||||
|
*/
|
||||||
|
public enum GeomCmd {
|
||||||
|
MoveTo(1, 2),
|
||||||
|
LineTo(2, 2),
|
||||||
|
ClosePath(7, 0);
|
||||||
|
|
||||||
|
/** Unique command ID */
|
||||||
|
private final int cmdId;
|
||||||
|
|
||||||
|
/** Amount of parameters that follow the command */
|
||||||
|
private final int paramCount;
|
||||||
|
|
||||||
|
GeomCmd(int cmdId, int paramCount) {
|
||||||
|
this.cmdId = cmdId;
|
||||||
|
this.paramCount = paramCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return unique command ID
|
||||||
|
*/
|
||||||
|
public int getCmdId() {
|
||||||
|
return cmdId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return amount of parameters that follow the command
|
||||||
|
*/
|
||||||
|
public int getParamCount() {
|
||||||
|
return paramCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return matching {@link GeomCmd} for the provided cmdId, or null if there is not
|
||||||
|
* a matching command.
|
||||||
|
*
|
||||||
|
* @param cmdId command id to find match for
|
||||||
|
* @return command with matching id, or null if there is not a matching command
|
||||||
|
*/
|
||||||
|
public static GeomCmd fromId(int cmdId) {
|
||||||
|
final GeomCmd geomCmd;
|
||||||
|
switch (cmdId) {
|
||||||
|
case 1:
|
||||||
|
geomCmd = MoveTo;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
geomCmd = LineTo;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
geomCmd = ClosePath;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
geomCmd = null;
|
||||||
|
}
|
||||||
|
return geomCmd;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.encoding;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for working with geometry command headers.
|
||||||
|
*
|
||||||
|
* @see GeomCmd
|
||||||
|
*/
|
||||||
|
public final class GeomCmdHdr {
|
||||||
|
|
||||||
|
private static int CLOSE_PATH_HDR = cmdHdr(GeomCmd.ClosePath, 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Encodes a 'command header' with the first 3 LSB as the command id, the remaining bits
|
||||||
|
* as the command length. See the vector-tile-spec for details.</p>
|
||||||
|
*
|
||||||
|
* @param cmd command to execute
|
||||||
|
* @param length how many times the command is repeated
|
||||||
|
* @return encoded 'command header' integer
|
||||||
|
*/
|
||||||
|
public static int cmdHdr(GeomCmd cmd, int length) {
|
||||||
|
return (cmd.getCmdId() & 0x7) | (length << 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the length component from the 'command header' integer.
|
||||||
|
*
|
||||||
|
* @param cmdHdr encoded 'command header' integer
|
||||||
|
* @return command length
|
||||||
|
*/
|
||||||
|
public static int getCmdLength(int cmdHdr) {
|
||||||
|
return cmdHdr >> 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id component from the 'command header' integer.
|
||||||
|
*
|
||||||
|
* @param cmdHdr encoded 'command header' integer
|
||||||
|
* @return command id
|
||||||
|
*/
|
||||||
|
public static int getCmdId(int cmdHdr) {
|
||||||
|
return cmdHdr & 0x7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id component from the 'command header' integer, then find the
|
||||||
|
* {@link GeomCmd} with a matching id.
|
||||||
|
*
|
||||||
|
* @param cmdHdr encoded 'command header' integer
|
||||||
|
* @return command with matching id, or null if a match could not be made
|
||||||
|
*/
|
||||||
|
public static GeomCmd getCmd(int cmdHdr) {
|
||||||
|
final int cmdId = getCmdId(cmdHdr);
|
||||||
|
return GeomCmd.fromId(cmdId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return encoded 'command header' integer for {@link GeomCmd#ClosePath}.
|
||||||
|
*/
|
||||||
|
public static int closePathCmdHdr() {
|
||||||
|
return CLOSE_PATH_HDR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum allowed 'command header' length value.
|
||||||
|
*/
|
||||||
|
public static final int CMD_HDR_LEN_MAX = (int) (Math.pow(2, 29) - 1);
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.encoding;
|
||||||
|
|
||||||
|
|
||||||
|
import net.osmand.binary.VectorTile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Useful misc operations for encoding 'Mapbox Vector Tiles'.</p>
|
||||||
|
*
|
||||||
|
* <p>See: <a href="https://github.com/mapbox/vector-tile-spec">https://github.com/mapbox/vector-tile-spec</a></p>
|
||||||
|
*/
|
||||||
|
public final class MvtUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the MVT geometry type should be closed with a {@link GeomCmd#ClosePath}.
|
||||||
|
*
|
||||||
|
* @param geomType the type of MVT geometry
|
||||||
|
* @return true if the geometry should be closed, false if it should not be closed
|
||||||
|
*/
|
||||||
|
public static boolean shouldClosePath(VectorTile.Tile.GeomType geomType) {
|
||||||
|
final boolean closeReq;
|
||||||
|
|
||||||
|
switch(geomType) {
|
||||||
|
case POLYGON:
|
||||||
|
closeReq = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
closeReq = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return closeReq;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.encoding;
|
||||||
|
|
||||||
|
|
||||||
|
import net.osmand.binary.VectorTile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for working with {@link VectorTile.Tile.Value} instances.
|
||||||
|
*
|
||||||
|
* @see VectorTile.Tile.Value
|
||||||
|
*/
|
||||||
|
public final class MvtValue {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Covert an {@link Object} to a new {@link VectorTile.Tile.Value} instance.
|
||||||
|
*
|
||||||
|
* @param value target for conversion
|
||||||
|
* @return new instance with String or primitive value set
|
||||||
|
*/
|
||||||
|
public static VectorTile.Tile.Value toValue(Object value) {
|
||||||
|
final VectorTile.Tile.Value.Builder tileValue = VectorTile.Tile.Value.newBuilder();
|
||||||
|
|
||||||
|
if(value instanceof Boolean) {
|
||||||
|
tileValue.setBoolValue((Boolean) value);
|
||||||
|
|
||||||
|
} else if(value instanceof Integer) {
|
||||||
|
tileValue.setSintValue((Integer) value);
|
||||||
|
|
||||||
|
} else if(value instanceof Long) {
|
||||||
|
tileValue.setSintValue((Long) value);
|
||||||
|
|
||||||
|
} else if(value instanceof Float) {
|
||||||
|
tileValue.setFloatValue((Float) value);
|
||||||
|
|
||||||
|
} else if(value instanceof Double) {
|
||||||
|
tileValue.setDoubleValue((Double) value);
|
||||||
|
|
||||||
|
} else if(value instanceof String) {
|
||||||
|
tileValue.setStringValue((String) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tileValue.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert {@link VectorTile.Tile.Value} to String or boxed primitive object.
|
||||||
|
*
|
||||||
|
* @param value target for conversion
|
||||||
|
* @return String or boxed primitive
|
||||||
|
*/
|
||||||
|
public static Object toObject(VectorTile.Tile.Value value) {
|
||||||
|
Object result = null;
|
||||||
|
|
||||||
|
if(value.hasDoubleValue()) {
|
||||||
|
result = value.getDoubleValue();
|
||||||
|
|
||||||
|
} else if(value.hasFloatValue()) {
|
||||||
|
result = value.getFloatValue();
|
||||||
|
|
||||||
|
} else if(value.hasIntValue()) {
|
||||||
|
result = value.getIntValue();
|
||||||
|
|
||||||
|
} else if(value.hasBoolValue()) {
|
||||||
|
result = value.getBoolValue();
|
||||||
|
|
||||||
|
} else if(value.hasStringValue()) {
|
||||||
|
result = value.getStringValue();
|
||||||
|
|
||||||
|
} else if(value.hasSintValue()) {
|
||||||
|
result = value.getSintValue();
|
||||||
|
|
||||||
|
} else if(value.hasUintValue()) {
|
||||||
|
result = value.getUintValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if {@code value} is valid for encoding as a MVT layer property value.
|
||||||
|
*
|
||||||
|
* @param value target to check
|
||||||
|
* @return true is the object is a type that is supported by MVT
|
||||||
|
*/
|
||||||
|
public static boolean isValidPropValue(Object value) {
|
||||||
|
boolean isValid = false;
|
||||||
|
|
||||||
|
if(value instanceof Boolean || value instanceof Integer || value instanceof Long
|
||||||
|
|| value instanceof Float || value instanceof Double || value instanceof String) {
|
||||||
|
isValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.encoding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See: <a href="https://developers.google.com/protocol-buffers/docs/encoding#types">Google Protocol Buffers Docs</a>
|
||||||
|
*/
|
||||||
|
public final class ZigZag {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See: <a href="https://developers.google.com/protocol-buffers/docs/encoding#types">Google Protocol Buffers Docs</a>
|
||||||
|
*
|
||||||
|
* @param n integer to encode
|
||||||
|
* @return zig-zag encoded integer
|
||||||
|
*/
|
||||||
|
public static int encode(int n) {
|
||||||
|
return (n << 1) ^ (n >> 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See: <a href="https://developers.google.com/protocol-buffers/docs/encoding#types">Google Protocol Buffers Docs</a>
|
||||||
|
*
|
||||||
|
* @param n zig-zag encoded integer to decode
|
||||||
|
* @return decoded integer
|
||||||
|
*/
|
||||||
|
public static int decode(int n) {
|
||||||
|
return (n >> 1) ^ (-(n & 1));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
package com.wdtinc.mapbox_vector_tile.util;
|
||||||
|
|
||||||
|
import com.vividsolutions.jts.geom.*;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.adapt.jts.JtsAdapter;
|
||||||
|
|
||||||
|
import net.osmand.binary.VectorTile;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public final class JtsGeomStats {
|
||||||
|
|
||||||
|
public static final class FeatureStats {
|
||||||
|
public int totalPts;
|
||||||
|
public int repeatedPts;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "FeatureStats{" +
|
||||||
|
"totalPts=" + totalPts +
|
||||||
|
", repeatedPts=" + repeatedPts +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<VectorTile.Tile.GeomType, Integer> featureCounts;
|
||||||
|
public List<FeatureStats> featureStats;
|
||||||
|
|
||||||
|
private JtsGeomStats() {
|
||||||
|
final VectorTile.Tile.GeomType[] geomTypes = VectorTile.Tile.GeomType.values();
|
||||||
|
featureCounts = new HashMap<>(geomTypes.length);
|
||||||
|
|
||||||
|
for(VectorTile.Tile.GeomType nextGeomType : geomTypes) {
|
||||||
|
featureCounts.put(nextGeomType, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.featureStats = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "JtsGeomStats{" +
|
||||||
|
"featureCounts=" + featureCounts +
|
||||||
|
", featureStats=" + featureStats +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JtsGeomStats getStats(List<Geometry> flatGeomList) {
|
||||||
|
final JtsGeomStats stats = new JtsGeomStats();
|
||||||
|
|
||||||
|
for(Geometry nextGeom : flatGeomList) {
|
||||||
|
final VectorTile.Tile.GeomType geomType = JtsAdapter.toGeomType(nextGeom);
|
||||||
|
|
||||||
|
// Count features by type
|
||||||
|
Integer oldValue = stats.featureCounts.get(geomType);
|
||||||
|
if(oldValue!=null){
|
||||||
|
oldValue+=1;
|
||||||
|
stats.featureCounts.put(geomType, oldValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get stats per feature
|
||||||
|
stats.featureStats.add(getStats(nextGeom, geomType));
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FeatureStats getStats(Geometry geom, VectorTile.Tile.GeomType type) {
|
||||||
|
FeatureStats featureStats;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case POINT:
|
||||||
|
featureStats = pointStats(geom);
|
||||||
|
break;
|
||||||
|
case LINESTRING:
|
||||||
|
featureStats = lineStats(geom);
|
||||||
|
break;
|
||||||
|
case POLYGON:
|
||||||
|
featureStats = polyStats(geom);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
featureStats = new FeatureStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
return featureStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FeatureStats pointStats(Geometry geom) {
|
||||||
|
final FeatureStats featureStats = new FeatureStats();
|
||||||
|
|
||||||
|
final HashSet<Point> pointSet = new HashSet<>(geom.getNumPoints());
|
||||||
|
featureStats.totalPts = geom.getNumPoints();
|
||||||
|
|
||||||
|
for(int i = 0; i < geom.getNumGeometries(); ++i) {
|
||||||
|
final Point p = (Point) geom.getGeometryN(i);
|
||||||
|
featureStats.repeatedPts += pointSet.add(p) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return featureStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FeatureStats lineStats(Geometry geom) {
|
||||||
|
final FeatureStats featureStats = new FeatureStats();
|
||||||
|
|
||||||
|
for(int i = 0; i < geom.getNumGeometries(); ++i) {
|
||||||
|
final LineString lineString = (LineString) geom.getGeometryN(i);
|
||||||
|
featureStats.totalPts += lineString.getNumPoints();
|
||||||
|
featureStats.repeatedPts += checkRepeatedPoints2d(lineString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return featureStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FeatureStats polyStats(Geometry geom) {
|
||||||
|
final FeatureStats featureStats = new FeatureStats();
|
||||||
|
|
||||||
|
for(int i = 0; i < geom.getNumGeometries(); ++i) {
|
||||||
|
final Polygon nextPoly = (Polygon) geom.getGeometryN(i);
|
||||||
|
|
||||||
|
// Stats: exterior ring
|
||||||
|
final LineString exteriorRing = nextPoly.getExteriorRing();
|
||||||
|
featureStats.totalPts += exteriorRing.getNumPoints();
|
||||||
|
featureStats.repeatedPts += checkRepeatedPoints2d(exteriorRing);
|
||||||
|
|
||||||
|
// Stats: interior rings
|
||||||
|
for(int ringIndex = 0; ringIndex < nextPoly.getNumInteriorRing(); ++ringIndex) {
|
||||||
|
|
||||||
|
final LineString nextInteriorRing = nextPoly.getInteriorRingN(ringIndex);
|
||||||
|
featureStats.totalPts += nextInteriorRing.getNumPoints();
|
||||||
|
featureStats.repeatedPts += checkRepeatedPoints2d(nextInteriorRing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return featureStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int checkRepeatedPoints2d(LineString lineString) {
|
||||||
|
int repeatedPoints = 0;
|
||||||
|
|
||||||
|
final CoordinateSequence coordSeq = lineString.getCoordinateSequence();
|
||||||
|
Coordinate nextCoord = null, prevCoord;
|
||||||
|
for(int i = 0; i < coordSeq.size(); ++i) {
|
||||||
|
prevCoord = nextCoord;
|
||||||
|
nextCoord = coordSeq.getCoordinate(i);
|
||||||
|
if(nextCoord.equals(prevCoord)) {
|
||||||
|
++repeatedPoints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return repeatedPoints;
|
||||||
|
}
|
||||||
|
}
|
126
OsmAnd-java/src/com/wdtinc/mapbox_vector_tile/util/Vec2d.java
Normal file
126
OsmAnd-java/src/com/wdtinc/mapbox_vector_tile/util/Vec2d.java
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
|
||||||
|
package com.wdtinc.mapbox_vector_tile.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutable Vector with double-valued x and y dimensions.
|
||||||
|
*/
|
||||||
|
public final class Vec2d {
|
||||||
|
|
||||||
|
public double x, y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct instance with x = 0, y = 0.
|
||||||
|
*/
|
||||||
|
public Vec2d() {
|
||||||
|
set(0d, 0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct instance with (x, y) values set to passed parameters
|
||||||
|
*
|
||||||
|
* @param x value in x
|
||||||
|
* @param y value in y
|
||||||
|
*/
|
||||||
|
public Vec2d(double x, double y) {
|
||||||
|
set(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs instance with values from the input vector 'v'.
|
||||||
|
*
|
||||||
|
* @param v The vector
|
||||||
|
*/
|
||||||
|
public Vec2d(Vec2d v) {
|
||||||
|
set(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the x and y values of this vector. Return this vector for chaining.
|
||||||
|
*
|
||||||
|
* @param x value in x
|
||||||
|
* @param y value in y
|
||||||
|
* @return this vector for chaining
|
||||||
|
*/
|
||||||
|
public Vec2d set(double x, double y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the x and y values of this vector to match input vector 'v'. Return this vector for chaining.
|
||||||
|
*
|
||||||
|
* @param v contains values to copy
|
||||||
|
* @return this vector for chaining
|
||||||
|
*/
|
||||||
|
public Vec2d set(Vec2d v) {
|
||||||
|
return set(v.x, v.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given values to this vector. Return this vector for chaining.
|
||||||
|
*
|
||||||
|
* @param x value in x
|
||||||
|
* @param y value in y
|
||||||
|
* @return this vector for chaining
|
||||||
|
*/
|
||||||
|
public Vec2d add(double x, double y) {
|
||||||
|
this.x += x;
|
||||||
|
this.y += y;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given vector 'v' to this vector. Return this vector for chaining.
|
||||||
|
*
|
||||||
|
* @param v vector to add
|
||||||
|
* @return this vector for chaining
|
||||||
|
*/
|
||||||
|
public Vec2d add(Vec2d v) {
|
||||||
|
return add(v.x, v.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts the given values from this vector. Return this vector for chaining.
|
||||||
|
*
|
||||||
|
* @param x value in x to subtract
|
||||||
|
* @param y value in y to subtract
|
||||||
|
* @return this vector for chaining
|
||||||
|
*/
|
||||||
|
public Vec2d sub(double x, double y) {
|
||||||
|
this.x -= x;
|
||||||
|
this.y -= y;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts the given vector 'v' from this vector. Return this vector for chaining.
|
||||||
|
*
|
||||||
|
* @param v vector to subtract
|
||||||
|
* @return this vector for chaining
|
||||||
|
*/
|
||||||
|
public Vec2d sub(Vec2d v) {
|
||||||
|
return sub(v.x, v.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scales this vector's values by a constant.
|
||||||
|
*
|
||||||
|
* @param scalar constant to scale this vector's values by
|
||||||
|
* @return this vector for chaining
|
||||||
|
*/
|
||||||
|
public Vec2d scale(double scalar) {
|
||||||
|
this.x *= scalar;
|
||||||
|
this.y *= scalar;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString () {
|
||||||
|
return "(" + x + "," + y + ")";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package net.osmand.binary;
|
||||||
|
|
||||||
|
import com.vividsolutions.jts.geom.Geometry;
|
||||||
|
import com.vividsolutions.jts.geom.GeometryFactory;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.adapt.jts.MvtReader;
|
||||||
|
import com.wdtinc.mapbox_vector_tile.adapt.jts.TagKeyValueMapConverter;
|
||||||
|
|
||||||
|
import net.osmand.data.GeometryTile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BinaryVectorTileReader {
|
||||||
|
|
||||||
|
public static GeometryTile readTile(File file) throws IOException {
|
||||||
|
GeometryFactory geomFactory = new GeometryFactory();
|
||||||
|
return new GeometryTile(
|
||||||
|
MvtReader.loadMvt(new FileInputStream(file), geomFactory, new TagKeyValueMapConverter()));
|
||||||
|
}
|
||||||
|
}
|
4942
OsmAnd-java/src/net/osmand/binary/VectorTile.java
Normal file
4942
OsmAnd-java/src/net/osmand/binary/VectorTile.java
Normal file
File diff suppressed because it is too large
Load diff
18
OsmAnd-java/src/net/osmand/data/GeometryTile.java
Normal file
18
OsmAnd-java/src/net/osmand/data/GeometryTile.java
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package net.osmand.data;
|
||||||
|
|
||||||
|
import com.vividsolutions.jts.geom.Geometry;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GeometryTile {
|
||||||
|
|
||||||
|
private List<Geometry> data;
|
||||||
|
|
||||||
|
public GeometryTile(List<Geometry> data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Geometry> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,11 +40,14 @@ public class TileSourceManager {
|
||||||
new TileSourceTemplate("OsmAnd (online tiles)", "http://tile.osmand.net/hd/{0}/{1}/{2}.png", ".png", 19, 1, 512, 8, 18000); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
|
new TileSourceTemplate("OsmAnd (online tiles)", "http://tile.osmand.net/hd/{0}/{1}/{2}.png", ".png", 19, 1, 512, 8, 18000); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
|
||||||
private static final TileSourceTemplate CYCLE_MAP_SOURCE =
|
private static final TileSourceTemplate CYCLE_MAP_SOURCE =
|
||||||
new TileSourceTemplate("CycleMap", "http://b.tile.opencyclemap.org/cycle/{0}/{1}/{2}.png", ".png", 16, 1, 256, 32, 18000); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
|
new TileSourceTemplate("CycleMap", "http://b.tile.opencyclemap.org/cycle/{0}/{1}/{2}.png", ".png", 16, 1, 256, 32, 18000); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
|
||||||
private static final TileSourceTemplate MAPILLARY_SOURCE =
|
private static final TileSourceTemplate MAPILLARY_RASTER_SOURCE =
|
||||||
new TileSourceTemplate("Mapillary (raster tiles)", "https://d6a1v2w10ny40.cloudfront.net/v0.1/{0}/{1}/{2}.png", ".png", 17, 0, 256, 16, 32000);
|
new TileSourceTemplate("Mapillary (raster tiles)", "https://d6a1v2w10ny40.cloudfront.net/v0.1/{0}/{1}/{2}.png", ".png", 14, 0, 256, 16, 32000);
|
||||||
|
private static final TileSourceTemplate MAPILLARY_VECTOR_SOURCE =
|
||||||
|
new TileSourceTemplate("Mapillary (vector tiles)", "https://d25uarhxywzl1j.cloudfront.net/v0.1/{0}/{1}/{2}.mvt", ".mvt", 21, 15, 256, 16, 3200);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
MAPILLARY_SOURCE.setExpirationTimeMinutes(60 * 24);
|
MAPILLARY_RASTER_SOURCE.setExpirationTimeMinutes(60 * 24);
|
||||||
|
MAPILLARY_VECTOR_SOURCE.setExpirationTimeMinutes(60 * 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TileSourceTemplate implements ITileSource, Cloneable {
|
public static class TileSourceTemplate implements ITileSource, Cloneable {
|
||||||
|
@ -424,7 +427,8 @@ public class TileSourceManager {
|
||||||
java.util.List<TileSourceTemplate> list = new ArrayList<TileSourceTemplate>();
|
java.util.List<TileSourceTemplate> list = new ArrayList<TileSourceTemplate>();
|
||||||
list.add(getMapnikSource());
|
list.add(getMapnikSource());
|
||||||
list.add(getCycleMapSource());
|
list.add(getCycleMapSource());
|
||||||
list.add(getMapillarySource());
|
list.add(getMapillaryRasterSource());
|
||||||
|
list.add(getMapillaryVectorSource());
|
||||||
return list;
|
return list;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -437,8 +441,12 @@ public class TileSourceManager {
|
||||||
return CYCLE_MAP_SOURCE;
|
return CYCLE_MAP_SOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TileSourceTemplate getMapillarySource() {
|
public static TileSourceTemplate getMapillaryRasterSource() {
|
||||||
return MAPILLARY_SOURCE;
|
return MAPILLARY_RASTER_SOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TileSourceTemplate getMapillaryVectorSource() {
|
||||||
|
return MAPILLARY_VECTOR_SOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<TileSourceTemplate> downloadTileSourceTemplates(String versionAsUrl) {
|
public static List<TileSourceTemplate> downloadTileSourceTemplates(String versionAsUrl) {
|
||||||
|
|
BIN
OsmAnd/libs/jts-core-1.14.0.jar
Normal file
BIN
OsmAnd/libs/jts-core-1.14.0.jar
Normal file
Binary file not shown.
|
@ -1,26 +1,22 @@
|
||||||
package net.osmand.core.android;
|
package net.osmand.core.android;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import net.osmand.IndexConstants;
|
import net.osmand.IndexConstants;
|
||||||
import net.osmand.core.jni.AlphaChannelPresence;
|
import net.osmand.core.jni.AlphaChannelPresence;
|
||||||
import net.osmand.core.jni.IMapDataProvider;
|
import net.osmand.core.jni.IMapTiledDataProvider;
|
||||||
|
import net.osmand.core.jni.ImageMapLayerProvider;
|
||||||
import net.osmand.core.jni.MapStubStyle;
|
import net.osmand.core.jni.MapStubStyle;
|
||||||
import net.osmand.core.jni.SWIGTYPE_p_QByteArray;
|
import net.osmand.core.jni.SWIGTYPE_p_QByteArray;
|
||||||
import net.osmand.core.jni.SwigUtilities;
|
import net.osmand.core.jni.SwigUtilities;
|
||||||
import net.osmand.core.jni.TileId;
|
|
||||||
import net.osmand.core.jni.ZoomLevel;
|
import net.osmand.core.jni.ZoomLevel;
|
||||||
import net.osmand.core.jni.interface_ImageMapLayerProvider;
|
import net.osmand.core.jni.interface_ImageMapLayerProvider;
|
||||||
import net.osmand.core.jni.IMapTiledDataProvider;
|
|
||||||
import net.osmand.core.jni.ImageMapLayerProvider;
|
|
||||||
import net.osmand.map.ITileSource;
|
import net.osmand.map.ITileSource;
|
||||||
import net.osmand.map.MapTileDownloader;
|
import net.osmand.map.MapTileDownloader;
|
||||||
import net.osmand.plus.OsmandApplication;
|
import net.osmand.plus.OsmandApplication;
|
||||||
import net.osmand.plus.resources.AsyncLoadingThread;
|
import net.osmand.plus.resources.AsyncLoadingThread;
|
||||||
import net.osmand.plus.resources.ResourceManager;
|
import net.osmand.plus.resources.ResourceManager;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public class TileSourceProxyProvider extends interface_ImageMapLayerProvider {
|
public class TileSourceProxyProvider extends interface_ImageMapLayerProvider {
|
||||||
|
|
||||||
private final OsmandApplication app;
|
private final OsmandApplication app;
|
||||||
|
@ -62,7 +58,7 @@ public class TileSourceProxyProvider extends interface_ImageMapLayerProvider {
|
||||||
final TileReadyCallback tileReadyCallback = new TileReadyCallback(tileSource,
|
final TileReadyCallback tileReadyCallback = new TileReadyCallback(tileSource,
|
||||||
request.getTileId().getX(), request.getTileId().getY(), request.getZoom().swigValue());
|
request.getTileId().getX(), request.getTileId().getY(), request.getZoom().swigValue());
|
||||||
rm.getMapTileDownloader().addDownloaderCallback(tileReadyCallback);
|
rm.getMapTileDownloader().addDownloaderCallback(tileReadyCallback);
|
||||||
while (rm.getTileImageForMapAsync(tileFilename, tileSource, request.getTileId().getX(), request.getTileId().getY(),
|
while (rm.getBitmapTilesCache().getTileForMapAsync(tileFilename, tileSource, request.getTileId().getX(), request.getTileId().getY(),
|
||||||
request.getZoom().swigValue(), true) == null) {
|
request.getZoom().swigValue(), true) == null) {
|
||||||
synchronized (tileReadyCallback.getSync()) {
|
synchronized (tileReadyCallback.getSync()) {
|
||||||
if (tileReadyCallback.isReady()) {
|
if (tileReadyCallback.isReady()) {
|
||||||
|
|
|
@ -166,7 +166,7 @@ public class DownloadTilesDialog {
|
||||||
if (rm.tileExistOnFileSystem(tileId, map, x, y, z)) {
|
if (rm.tileExistOnFileSystem(tileId, map, x, y, z)) {
|
||||||
progressDlg.setProgress(progressDlg.getProgress() + 1);
|
progressDlg.setProgress(progressDlg.getProgress() + 1);
|
||||||
} else {
|
} else {
|
||||||
rm.getTileImageForMapSync(tileId, map, x, y, z, true);
|
rm.hasTileForMapSync(tileId, map, x, y, z, true);
|
||||||
requests++;
|
requests++;
|
||||||
}
|
}
|
||||||
if (!cancel) {
|
if (!cancel) {
|
||||||
|
|
|
@ -540,7 +540,7 @@ public class MapActivityActions implements DialogProvider {
|
||||||
for (int i = 0; i < width; i++) {
|
for (int i = 0; i < width; i++) {
|
||||||
for (int j = 0; j < height; j++) {
|
for (int j = 0; j < height; j++) {
|
||||||
((OsmandApplication) mapActivity.getApplication()).getResourceManager().
|
((OsmandApplication) mapActivity.getApplication()).getResourceManager().
|
||||||
clearTileImageForMap(null, mapSource, i + left, j + top, zoom);
|
clearTileForMap(null, mapSource, i + left, j + top, zoom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
OsmAnd/src/net/osmand/plus/mapillary/MapillaryImage.java
Normal file
15
OsmAnd/src/net/osmand/plus/mapillary/MapillaryImage.java
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package net.osmand.plus.mapillary;
|
||||||
|
|
||||||
|
public class MapillaryImage {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ca number Camera heading. -1 if not found.
|
||||||
|
* captured_at number When the image was captured, expressed as UTC epoch time in milliseconds. Must be non-negative integer; 0 if not found.
|
||||||
|
* key Key Image key.
|
||||||
|
* pano number Whether the image is panorama ( 1 ), or not ( 0 ).
|
||||||
|
* skey Key Sequence key.
|
||||||
|
* userkey Key User key. Empty if not found.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private double ca = Double.NaN;
|
||||||
|
}
|
|
@ -119,15 +119,8 @@ public class MapillaryImageDialog extends ContextMenuCardDialog {
|
||||||
|
|
||||||
private void setImageLocation(LatLon latLon, double ca, boolean animated) {
|
private void setImageLocation(LatLon latLon, double ca, boolean animated) {
|
||||||
OsmandMapTileView mapView = getMapActivity().getMapView();
|
OsmandMapTileView mapView = getMapActivity().getMapView();
|
||||||
MapillaryLayer layer = mapView.getLayerByClass(MapillaryLayer.class);
|
updateLayer(mapView.getLayerByClass(MapillaryRasterLayer.class), latLon, ca);
|
||||||
if (layer != null) {
|
updateLayer(mapView.getLayerByClass(MapillaryVectorLayer.class), latLon, ca);
|
||||||
layer.setSelectedImageLocation(latLon);
|
|
||||||
if (!Double.isNaN(ca)) {
|
|
||||||
layer.setSelectedImageCameraAngle((float) ca);
|
|
||||||
} else {
|
|
||||||
layer.setSelectedImageCameraAngle(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (latLon != null) {
|
if (latLon != null) {
|
||||||
if (animated) {
|
if (animated) {
|
||||||
mapView.getAnimatedDraggingThread().startMoving(
|
mapView.getAnimatedDraggingThread().startMoving(
|
||||||
|
@ -140,6 +133,17 @@ public class MapillaryImageDialog extends ContextMenuCardDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateLayer(MapillaryLayer layer, LatLon latLon, double ca) {
|
||||||
|
if (layer != null) {
|
||||||
|
layer.setSelectedImageLocation(latLon);
|
||||||
|
if (!Double.isNaN(ca)) {
|
||||||
|
layer.setSelectedImageCameraAngle((float) ca);
|
||||||
|
} else {
|
||||||
|
layer.setSelectedImageCameraAngle(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public View getContentView() {
|
public View getContentView() {
|
||||||
if (getMapActivity().getMyApplication().getSettings().WEBGL_SUPPORTED.get()) {
|
if (getMapActivity().getMyApplication().getSettings().WEBGL_SUPPORTED.get()) {
|
||||||
return getWebView();
|
return getWebView();
|
||||||
|
|
|
@ -1,69 +1,10 @@
|
||||||
package net.osmand.plus.mapillary;
|
package net.osmand.plus.mapillary;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.graphics.PorterDuffColorFilter;
|
|
||||||
|
|
||||||
import net.osmand.data.LatLon;
|
import net.osmand.data.LatLon;
|
||||||
import net.osmand.data.RotatedTileBox;
|
|
||||||
import net.osmand.plus.R;
|
|
||||||
import net.osmand.plus.views.MapTileLayer;
|
|
||||||
import net.osmand.plus.views.OsmandMapLayer;
|
|
||||||
import net.osmand.plus.views.OsmandMapTileView;
|
|
||||||
|
|
||||||
public class MapillaryLayer extends MapTileLayer {
|
interface MapillaryLayer {
|
||||||
|
|
||||||
private LatLon selectedImageLocation;
|
void setSelectedImageLocation(LatLon selectedImageLocation);
|
||||||
private Float selectedImageCameraAngle;
|
|
||||||
private Bitmap selectedImage;
|
|
||||||
private Bitmap headingImage;
|
|
||||||
private Paint paintIcon;
|
|
||||||
|
|
||||||
public MapillaryLayer() {
|
void setSelectedImageCameraAngle(Float selectedImageCameraAngle);
|
||||||
super(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initLayer(OsmandMapTileView view) {
|
|
||||||
super.initLayer(view);
|
|
||||||
paintIcon = new Paint();
|
|
||||||
selectedImage = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_default_location);
|
|
||||||
headingImage = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_pedestrian_location_view_angle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LatLon getSelectedImageLocation() {
|
|
||||||
return selectedImageLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectedImageLocation(LatLon selectedImageLocation) {
|
|
||||||
this.selectedImageLocation = selectedImageLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Float getSelectedImageCameraAngle() {
|
|
||||||
return selectedImageCameraAngle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectedImageCameraAngle(Float selectedImageCameraAngle) {
|
|
||||||
this.selectedImageCameraAngle = selectedImageCameraAngle;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings drawSettings) {
|
|
||||||
super.onPrepareBufferImage(canvas, tileBox, drawSettings);
|
|
||||||
if (selectedImageLocation != null) {
|
|
||||||
float x = tileBox.getPixXFromLatLon(selectedImageLocation.getLatitude(), selectedImageLocation.getLongitude());
|
|
||||||
float y = tileBox.getPixYFromLatLon(selectedImageLocation.getLatitude(), selectedImageLocation.getLongitude());
|
|
||||||
if (selectedImageCameraAngle != null) {
|
|
||||||
canvas.save();
|
|
||||||
canvas.rotate(selectedImageCameraAngle - 180, x, y);
|
|
||||||
canvas.drawBitmap(headingImage, x - headingImage.getWidth() / 2,
|
|
||||||
y - headingImage.getHeight() / 2, paintIcon);
|
|
||||||
canvas.restore();
|
|
||||||
}
|
|
||||||
canvas.drawBitmap(selectedImage, x - selectedImage.getWidth() / 2, y - selectedImage.getHeight() / 2, paintIcon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,8 @@ public class MapillaryPlugin extends OsmandPlugin {
|
||||||
private OsmandSettings settings;
|
private OsmandSettings settings;
|
||||||
private OsmandApplication app;
|
private OsmandApplication app;
|
||||||
|
|
||||||
private MapillaryLayer rasterLayer;
|
private MapillaryRasterLayer rasterLayer;
|
||||||
|
private MapillaryVectorLayer vectorLayer;
|
||||||
private TextInfoWidget mapillaryControl;
|
private TextInfoWidget mapillaryControl;
|
||||||
private MapWidgetRegInfo mapillaryWidgetRegInfo;
|
private MapWidgetRegInfo mapillaryWidgetRegInfo;
|
||||||
|
|
||||||
|
@ -82,7 +83,8 @@ public class MapillaryPlugin extends OsmandPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createLayers() {
|
private void createLayers() {
|
||||||
rasterLayer = new MapillaryLayer();
|
rasterLayer = new MapillaryRasterLayer();
|
||||||
|
vectorLayer = new MapillaryVectorLayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -91,23 +93,28 @@ public class MapillaryPlugin extends OsmandPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMapLayers(OsmandMapTileView mapView, final MapActivityLayers layers) {
|
private void updateMapLayers(OsmandMapTileView mapView, final MapActivityLayers layers) {
|
||||||
if (rasterLayer == null) {
|
if (rasterLayer == null || vectorLayer == null) {
|
||||||
createLayers();
|
createLayers();
|
||||||
}
|
}
|
||||||
if (isActive()) {
|
if (isActive()) {
|
||||||
updateLayer(mapView, rasterLayer, 0.6f);
|
ITileSource rasterSource = null;
|
||||||
|
ITileSource vectorSource = null;
|
||||||
|
if (settings.SHOW_MAPILLARY.get()) {
|
||||||
|
rasterSource = settings.getTileSourceByName(TileSourceManager.getMapillaryRasterSource().getName(), false);
|
||||||
|
vectorSource = settings.getTileSourceByName(TileSourceManager.getMapillaryVectorSource().getName(), false);
|
||||||
|
}
|
||||||
|
updateLayer(mapView, rasterSource, rasterLayer, 0.6f);
|
||||||
|
updateLayer(mapView, vectorSource, vectorLayer, 0.61f);
|
||||||
} else {
|
} else {
|
||||||
mapView.removeLayer(rasterLayer);
|
mapView.removeLayer(rasterLayer);
|
||||||
rasterLayer.setMap(null);
|
rasterLayer.setMap(null);
|
||||||
|
mapView.removeLayer(vectorLayer);
|
||||||
|
vectorLayer.setMap(null);
|
||||||
}
|
}
|
||||||
layers.updateMapSource(mapView, null);
|
layers.updateMapSource(mapView, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLayer(OsmandMapTileView mapView, MapTileLayer layer, float layerOrder) {
|
private void updateLayer(OsmandMapTileView mapView, ITileSource mapillarySource, MapTileLayer layer, float layerOrder) {
|
||||||
ITileSource mapillarySource = null;
|
|
||||||
if (settings.SHOW_MAPILLARY.get()) {
|
|
||||||
mapillarySource = settings.getTileSourceByName(TileSourceManager.getMapillarySource().getName(), false);
|
|
||||||
}
|
|
||||||
if (!Algorithms.objectEquals(mapillarySource, layer.getMap()) || !mapView.isLayerVisible(layer)) {
|
if (!Algorithms.objectEquals(mapillarySource, layer.getMap()) || !mapView.isLayerVisible(layer)) {
|
||||||
if (mapView.getMapRenderer() == null && !mapView.isLayerVisible(layer)) {
|
if (mapView.getMapRenderer() == null && !mapView.isLayerVisible(layer)) {
|
||||||
mapView.addLayer(layer, layerOrder);
|
mapView.addLayer(layer, layerOrder);
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package net.osmand.plus.mapillary;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
|
||||||
|
import net.osmand.data.LatLon;
|
||||||
|
import net.osmand.data.RotatedTileBox;
|
||||||
|
import net.osmand.map.ITileSource;
|
||||||
|
import net.osmand.plus.R;
|
||||||
|
import net.osmand.plus.views.MapTileLayer;
|
||||||
|
import net.osmand.plus.views.OsmandMapTileView;
|
||||||
|
|
||||||
|
class MapillaryRasterLayer extends MapTileLayer implements MapillaryLayer {
|
||||||
|
|
||||||
|
private LatLon selectedImageLocation;
|
||||||
|
private Float selectedImageCameraAngle;
|
||||||
|
private Bitmap selectedImage;
|
||||||
|
private Bitmap headingImage;
|
||||||
|
private Paint paintIcon;
|
||||||
|
|
||||||
|
MapillaryRasterLayer() {
|
||||||
|
super(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initLayer(OsmandMapTileView view) {
|
||||||
|
super.initLayer(view);
|
||||||
|
paintIcon = new Paint();
|
||||||
|
selectedImage = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_default_location);
|
||||||
|
headingImage = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_pedestrian_location_view_angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSelectedImageLocation(LatLon selectedImageLocation) {
|
||||||
|
this.selectedImageLocation = selectedImageLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSelectedImageCameraAngle(Float selectedImageCameraAngle) {
|
||||||
|
this.selectedImageCameraAngle = selectedImageCameraAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings drawSettings) {
|
||||||
|
super.onPrepareBufferImage(canvas, tileBox, drawSettings);
|
||||||
|
if (selectedImageLocation != null) {
|
||||||
|
float x = tileBox.getPixXFromLatLon(selectedImageLocation.getLatitude(), selectedImageLocation.getLongitude());
|
||||||
|
float y = tileBox.getPixYFromLatLon(selectedImageLocation.getLatitude(), selectedImageLocation.getLongitude());
|
||||||
|
if (selectedImageCameraAngle != null) {
|
||||||
|
canvas.save();
|
||||||
|
canvas.rotate(selectedImageCameraAngle - 180, x, y);
|
||||||
|
canvas.drawBitmap(headingImage, x - headingImage.getWidth() / 2,
|
||||||
|
y - headingImage.getHeight() / 2, paintIcon);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
canvas.drawBitmap(selectedImage, x - selectedImage.getWidth() / 2, y - selectedImage.getHeight() / 2, paintIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawTileMap(Canvas canvas, RotatedTileBox tileBox) {
|
||||||
|
ITileSource map = this.map;
|
||||||
|
if (map == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int maxZoom = map.getMaximumZoomSupported();
|
||||||
|
if (tileBox.getZoom() > maxZoom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.drawTileMap(canvas, tileBox);
|
||||||
|
}
|
||||||
|
}
|
160
OsmAnd/src/net/osmand/plus/mapillary/MapillaryVectorLayer.java
Normal file
160
OsmAnd/src/net/osmand/plus/mapillary/MapillaryVectorLayer.java
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
package net.osmand.plus.mapillary;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
|
||||||
|
import com.vividsolutions.jts.geom.Geometry;
|
||||||
|
import com.vividsolutions.jts.geom.Point;
|
||||||
|
|
||||||
|
import net.osmand.data.GeometryTile;
|
||||||
|
import net.osmand.data.LatLon;
|
||||||
|
import net.osmand.data.QuadRect;
|
||||||
|
import net.osmand.data.RotatedTileBox;
|
||||||
|
import net.osmand.map.ITileSource;
|
||||||
|
import net.osmand.plus.OsmandPlugin;
|
||||||
|
import net.osmand.plus.R;
|
||||||
|
import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin;
|
||||||
|
import net.osmand.plus.resources.ResourceManager;
|
||||||
|
import net.osmand.plus.views.MapTileLayer;
|
||||||
|
import net.osmand.plus.views.OsmandMapTileView;
|
||||||
|
import net.osmand.util.MapUtils;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class MapillaryVectorLayer extends MapTileLayer implements MapillaryLayer {
|
||||||
|
|
||||||
|
private static final int TILE_ZOOM = 14;
|
||||||
|
|
||||||
|
private LatLon selectedImageLocation;
|
||||||
|
private Float selectedImageCameraAngle;
|
||||||
|
private Bitmap selectedImage;
|
||||||
|
private Bitmap headingImage;
|
||||||
|
private Paint paintIcon;
|
||||||
|
private Bitmap point;
|
||||||
|
|
||||||
|
MapillaryVectorLayer() {
|
||||||
|
super(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initLayer(OsmandMapTileView view) {
|
||||||
|
super.initLayer(view);
|
||||||
|
paintIcon = new Paint();
|
||||||
|
selectedImage = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_default_location);
|
||||||
|
headingImage = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_pedestrian_location_view_angle);
|
||||||
|
point = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_note_small);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSelectedImageLocation(LatLon selectedImageLocation) {
|
||||||
|
this.selectedImageLocation = selectedImageLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSelectedImageCameraAngle(Float selectedImageCameraAngle) {
|
||||||
|
this.selectedImageCameraAngle = selectedImageCameraAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings drawSettings) {
|
||||||
|
super.onPrepareBufferImage(canvas, tileBox, drawSettings);
|
||||||
|
if (selectedImageLocation != null) {
|
||||||
|
float x = tileBox.getPixXFromLatLon(selectedImageLocation.getLatitude(), selectedImageLocation.getLongitude());
|
||||||
|
float y = tileBox.getPixYFromLatLon(selectedImageLocation.getLatitude(), selectedImageLocation.getLongitude());
|
||||||
|
if (selectedImageCameraAngle != null) {
|
||||||
|
canvas.save();
|
||||||
|
canvas.rotate(selectedImageCameraAngle - 180, x, y);
|
||||||
|
canvas.drawBitmap(headingImage, x - headingImage.getWidth() / 2,
|
||||||
|
y - headingImage.getHeight() / 2, paintIcon);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
canvas.drawBitmap(selectedImage, x - selectedImage.getWidth() / 2, y - selectedImage.getHeight() / 2, paintIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawTileMap(Canvas canvas, RotatedTileBox tileBox) {
|
||||||
|
ITileSource map = this.map;
|
||||||
|
if (map == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int nzoom = tileBox.getZoom();
|
||||||
|
if (nzoom < map.getMinimumZoomSupported()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ResourceManager mgr = resourceManager;
|
||||||
|
final QuadRect tilesRect = tileBox.getTileBounds();
|
||||||
|
|
||||||
|
// recalculate for ellipsoid coordinates
|
||||||
|
float ellipticTileCorrection = 0;
|
||||||
|
if (map.isEllipticYTile()) {
|
||||||
|
ellipticTileCorrection = (float) (MapUtils.getTileEllipsoidNumberY(nzoom, tileBox.getLatitude()) - tileBox.getCenterTileY());
|
||||||
|
}
|
||||||
|
|
||||||
|
int left = (int) Math.floor(tilesRect.left);
|
||||||
|
int top = (int) Math.floor(tilesRect.top + ellipticTileCorrection);
|
||||||
|
int width = (int) Math.ceil(tilesRect.right - left);
|
||||||
|
int height = (int) Math.ceil(tilesRect.bottom + ellipticTileCorrection - top);
|
||||||
|
|
||||||
|
boolean useInternet = (OsmandPlugin.getEnabledPlugin(OsmandRasterMapsPlugin.class) != null || OsmandPlugin.getEnabledPlugin(MapillaryPlugin.class) != null) &&
|
||||||
|
settings.USE_INTERNET_TO_DOWNLOAD_TILES.get() && settings.isInternetConnectionAvailable() && map.couldBeDownloadedFromInternet();
|
||||||
|
|
||||||
|
Map<String, GeometryTile> tiles = new LinkedHashMap<>();
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
for (int j = 0; j < height; j++) {
|
||||||
|
int leftPlusI = left + i;
|
||||||
|
int topPlusJ = top + j;
|
||||||
|
|
||||||
|
int x1 = tileBox.getPixXFromTileXNoRot(leftPlusI);
|
||||||
|
int x2 = tileBox.getPixXFromTileXNoRot(leftPlusI + 1);
|
||||||
|
|
||||||
|
int y1 = tileBox.getPixYFromTileYNoRot(topPlusJ - ellipticTileCorrection);
|
||||||
|
int y2 = tileBox.getPixYFromTileYNoRot(topPlusJ + 1 - ellipticTileCorrection);
|
||||||
|
bitmapToDraw.set(x1, y1, x2, y2);
|
||||||
|
|
||||||
|
int tileX = leftPlusI;
|
||||||
|
int tileY = topPlusJ;
|
||||||
|
|
||||||
|
//String tileId = mgr.calculateTileId(map, tileX, tileY, nzoom);
|
||||||
|
int dzoom = nzoom - TILE_ZOOM;
|
||||||
|
int div = (int) Math.pow(2.0, dzoom);
|
||||||
|
tileX /= div;
|
||||||
|
tileY /= div;
|
||||||
|
String tileId = mgr.calculateTileId(map, tileX, tileY, TILE_ZOOM);
|
||||||
|
GeometryTile tile = tiles.get(tileId);
|
||||||
|
if (tile == null) {
|
||||||
|
// asking tile image async
|
||||||
|
boolean imgExist = mgr.tileExistOnFileSystem(tileId, map, tileX, tileY, TILE_ZOOM);
|
||||||
|
if (imgExist || useInternet) {
|
||||||
|
tile = mgr.getGeometryTilesCache().getTileForMapAsync(tileId, map, tileX, tileY, TILE_ZOOM, useInternet);
|
||||||
|
}
|
||||||
|
if (tile != null) {
|
||||||
|
tiles.put(tileId, tile);
|
||||||
|
if (tile.getData() != null) {
|
||||||
|
drawPoints(canvas, tileBox, tileX, tileY, tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawPoints(Canvas canvas, RotatedTileBox tileBox, int tileX, int tileY, GeometryTile tile) {
|
||||||
|
for (Geometry g : tile.getData()) {
|
||||||
|
if (g instanceof Point && g.getCoordinate() != null) {
|
||||||
|
int x = (int) g.getCoordinate().x;
|
||||||
|
int y = (int) g.getCoordinate().y;
|
||||||
|
double lat = MapUtils.getLatitudeFromTile(TILE_ZOOM, tileY + y / 4096f);
|
||||||
|
double lon = MapUtils.getLongitudeFromTile(TILE_ZOOM, tileX + x / 4096f);
|
||||||
|
if (tileBox.containsLatLon(lat, lon)) {
|
||||||
|
float px = tileBox.getPixXFromLatLon(lat, lon);
|
||||||
|
float py = tileBox.getPixYFromLatLon(lat, lon);
|
||||||
|
canvas.drawBitmap(point, px - point.getWidth() / 2, py - point.getHeight() / 2, paintIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,6 @@
|
||||||
package net.osmand.plus.resources;
|
package net.osmand.plus.resources;
|
||||||
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Stack;
|
|
||||||
|
|
||||||
import net.osmand.PlatformUtil;
|
import net.osmand.PlatformUtil;
|
||||||
import net.osmand.ResultMatcher;
|
import net.osmand.ResultMatcher;
|
||||||
import net.osmand.data.RotatedTileBox;
|
import net.osmand.data.RotatedTileBox;
|
||||||
|
@ -17,6 +11,12 @@ import net.osmand.util.Algorithms;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thread to load map objects (POI, transport stops )async
|
* Thread to load map objects (POI, transport stops )async
|
||||||
*/
|
*/
|
||||||
|
@ -44,7 +44,7 @@ public class AsyncLoadingThread extends Thread {
|
||||||
Object req = requests.pop();
|
Object req = requests.pop();
|
||||||
if (req instanceof TileLoadDownloadRequest) {
|
if (req instanceof TileLoadDownloadRequest) {
|
||||||
TileLoadDownloadRequest r = (TileLoadDownloadRequest) req;
|
TileLoadDownloadRequest r = (TileLoadDownloadRequest) req;
|
||||||
tileLoaded |= resourceManger.getRequestedImageTile(r) != null;
|
tileLoaded |= resourceManger.hasRequestedTile(r);
|
||||||
} else if (req instanceof MapLoadRequest) {
|
} else if (req instanceof MapLoadRequest) {
|
||||||
if (!mapLoaded) {
|
if (!mapLoaded) {
|
||||||
MapLoadRequest r = (MapLoadRequest) req;
|
MapLoadRequest r = (MapLoadRequest) req;
|
||||||
|
@ -69,7 +69,7 @@ public class AsyncLoadingThread extends Thread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestToLoadImage(TileLoadDownloadRequest req) {
|
public void requestToLoadTile(TileLoadDownloadRequest req) {
|
||||||
requests.push(req);
|
requests.push(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
55
OsmAnd/src/net/osmand/plus/resources/BitmapTilesCache.java
Normal file
55
OsmAnd/src/net/osmand/plus/resources/BitmapTilesCache.java
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package net.osmand.plus.resources;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
|
||||||
|
import net.osmand.map.ITileSource;
|
||||||
|
import net.osmand.plus.SQLiteTileSource;
|
||||||
|
import net.osmand.plus.resources.AsyncLoadingThread.TileLoadDownloadRequest;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class BitmapTilesCache extends TilesCache<Bitmap> {
|
||||||
|
|
||||||
|
public BitmapTilesCache(AsyncLoadingThread asyncLoadingThread) {
|
||||||
|
super(asyncLoadingThread);
|
||||||
|
// it is not good investigated but no more than 64 (satellite images)
|
||||||
|
// Only 8 MB (from 16 Mb whole mem) available for images : image 64K * 128 = 8 MB (8 bit), 64 - 16 bit, 32 - 32 bit
|
||||||
|
// at least 3*9?
|
||||||
|
maxCacheSize = 28;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTileSourceSupported(ITileSource tileSource) {
|
||||||
|
return !".mvt".equals(tileSource.getTileFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Bitmap getTileObject(TileLoadDownloadRequest req) {
|
||||||
|
Bitmap bmp = null;
|
||||||
|
if (req.tileSource instanceof SQLiteTileSource) {
|
||||||
|
try {
|
||||||
|
long[] tm = new long[1];
|
||||||
|
bmp = ((SQLiteTileSource) req.tileSource).getImage(req.xTile, req.yTile, req.zoom, tm);
|
||||||
|
if (tm[0] != 0) {
|
||||||
|
downloadIfExpired(req, tm[0]);
|
||||||
|
}
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
log.error("Out of memory error", e); //$NON-NLS-1$
|
||||||
|
clearTiles();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
File en = new File(req.dirWithTiles, req.tileId);
|
||||||
|
if (en.exists()) {
|
||||||
|
try {
|
||||||
|
bmp = BitmapFactory.decodeFile(en.getAbsolutePath());
|
||||||
|
downloadIfExpired(req, en.lastModified());
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
log.error("Out of memory error", e); //$NON-NLS-1$
|
||||||
|
clearTiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bmp;
|
||||||
|
}
|
||||||
|
}
|
40
OsmAnd/src/net/osmand/plus/resources/GeometryTilesCache.java
Normal file
40
OsmAnd/src/net/osmand/plus/resources/GeometryTilesCache.java
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package net.osmand.plus.resources;
|
||||||
|
|
||||||
|
import net.osmand.binary.BinaryVectorTileReader;
|
||||||
|
import net.osmand.data.GeometryTile;
|
||||||
|
import net.osmand.map.ITileSource;
|
||||||
|
import net.osmand.plus.resources.AsyncLoadingThread.TileLoadDownloadRequest;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class GeometryTilesCache extends TilesCache<GeometryTile> {
|
||||||
|
|
||||||
|
public GeometryTilesCache(AsyncLoadingThread asyncLoadingThread) {
|
||||||
|
super(asyncLoadingThread);
|
||||||
|
maxCacheSize = 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTileSourceSupported(ITileSource tileSource) {
|
||||||
|
return ".mvt".equals(tileSource.getTileFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GeometryTile getTileObject(TileLoadDownloadRequest req) {
|
||||||
|
GeometryTile tile = null;
|
||||||
|
File en = new File(req.dirWithTiles, req.tileId);
|
||||||
|
if (en.exists()) {
|
||||||
|
try {
|
||||||
|
tile = BinaryVectorTileReader.readTile(en);
|
||||||
|
downloadIfExpired(req, en.lastModified());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Cannot read tile", e);
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
log.error("Out of memory error", e);
|
||||||
|
clearTiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,6 @@ package net.osmand.plus.resources;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.database.sqlite.SQLiteException;
|
import android.database.sqlite.SQLiteException;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
|
@ -35,7 +33,6 @@ import net.osmand.plus.AppInitializer.InitEvents;
|
||||||
import net.osmand.plus.OsmandApplication;
|
import net.osmand.plus.OsmandApplication;
|
||||||
import net.osmand.plus.OsmandPlugin;
|
import net.osmand.plus.OsmandPlugin;
|
||||||
import net.osmand.plus.R;
|
import net.osmand.plus.R;
|
||||||
import net.osmand.plus.SQLiteTileSource;
|
|
||||||
import net.osmand.plus.Version;
|
import net.osmand.plus.Version;
|
||||||
import net.osmand.plus.render.MapRenderRepositories;
|
import net.osmand.plus.render.MapRenderRepositories;
|
||||||
import net.osmand.plus.render.NativeOsmandLibrary;
|
import net.osmand.plus.render.NativeOsmandLibrary;
|
||||||
|
@ -80,24 +77,17 @@ public class ResourceManager {
|
||||||
public static final String VECTOR_MAP = "#vector_map"; //$NON-NLS-1$
|
public static final String VECTOR_MAP = "#vector_map"; //$NON-NLS-1$
|
||||||
private static final String INDEXES_CACHE = "ind.cache";
|
private static final String INDEXES_CACHE = "ind.cache";
|
||||||
|
|
||||||
|
|
||||||
private static final Log log = PlatformUtil.getLog(ResourceManager.class);
|
private static final Log log = PlatformUtil.getLog(ResourceManager.class);
|
||||||
|
|
||||||
|
|
||||||
protected static ResourceManager manager = null;
|
protected static ResourceManager manager = null;
|
||||||
|
|
||||||
// it is not good investigated but no more than 64 (satellite images)
|
|
||||||
// Only 8 MB (from 16 Mb whole mem) available for images : image 64K * 128 = 8 MB (8 bit), 64 - 16 bit, 32 - 32 bit
|
|
||||||
// at least 3*9?
|
|
||||||
protected int maxImgCacheSize = 28;
|
|
||||||
|
|
||||||
protected Map<String, Bitmap> cacheOfImages = new LinkedHashMap<String, Bitmap>();
|
|
||||||
protected Map<String, Boolean> imagesOnFS = new LinkedHashMap<String, Boolean>() ;
|
|
||||||
|
|
||||||
protected File dirWithTiles ;
|
protected File dirWithTiles ;
|
||||||
|
|
||||||
private final OsmandApplication context;
|
private List<TilesCache> tilesCacheList = new ArrayList<>();
|
||||||
|
private BitmapTilesCache bitmapTilesCache;
|
||||||
|
private GeometryTilesCache geometryTilesCache;
|
||||||
|
|
||||||
|
private final OsmandApplication context;
|
||||||
private List<ResourceListener> resourceListeners = new ArrayList<>();
|
private List<ResourceListener> resourceListeners = new ArrayList<>();
|
||||||
|
|
||||||
public interface ResourceListener {
|
public interface ResourceListener {
|
||||||
|
@ -105,7 +95,6 @@ public class ResourceManager {
|
||||||
void onMapsIndexed();
|
void onMapsIndexed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Indexes
|
// Indexes
|
||||||
public enum BinaryMapReaderResourceType {
|
public enum BinaryMapReaderResourceType {
|
||||||
POI,
|
POI,
|
||||||
|
@ -217,6 +206,12 @@ public class ResourceManager {
|
||||||
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.renderer = new MapRenderRepositories(context);
|
this.renderer = new MapRenderRepositories(context);
|
||||||
|
|
||||||
|
bitmapTilesCache = new BitmapTilesCache(asyncLoadingThread);
|
||||||
|
geometryTilesCache = new GeometryTilesCache(asyncLoadingThread);
|
||||||
|
tilesCacheList.add(bitmapTilesCache);
|
||||||
|
tilesCacheList.add(geometryTilesCache);
|
||||||
|
|
||||||
asyncLoadingThread.start();
|
asyncLoadingThread.start();
|
||||||
renderingBufferImageThread = new HandlerThread("RenderingBaseImage");
|
renderingBufferImageThread = new HandlerThread("RenderingBaseImage");
|
||||||
renderingBufferImageThread.start();
|
renderingBufferImageThread.start();
|
||||||
|
@ -224,14 +219,23 @@ public class ResourceManager {
|
||||||
tileDownloader = MapTileDownloader.getInstance(Version.getFullVersion(context));
|
tileDownloader = MapTileDownloader.getInstance(Version.getFullVersion(context));
|
||||||
dateFormat = DateFormat.getDateFormat(context);
|
dateFormat = DateFormat.getDateFormat(context);
|
||||||
resetStoreDirectory();
|
resetStoreDirectory();
|
||||||
|
|
||||||
WindowManager mgr = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
|
WindowManager mgr = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
|
||||||
DisplayMetrics dm = new DisplayMetrics();
|
DisplayMetrics dm = new DisplayMetrics();
|
||||||
mgr.getDefaultDisplay().getMetrics(dm);
|
mgr.getDefaultDisplay().getMetrics(dm);
|
||||||
// Only 8 MB (from 16 Mb whole mem) available for images : image 64K * 128 = 8 MB (8 bit), 64 - 16 bit, 32 - 32 bit
|
// Only 8 MB (from 16 Mb whole mem) available for images : image 64K * 128 = 8 MB (8 bit), 64 - 16 bit, 32 - 32 bit
|
||||||
// at least 3*9?
|
// at least 3*9?
|
||||||
float tiles = (dm.widthPixels / 256 + 2) * (dm.heightPixels / 256 + 2) * 3;
|
float tiles = (dm.widthPixels / 256 + 2) * (dm.heightPixels / 256 + 2) * 3;
|
||||||
log.info("Tiles to load in memory : " + tiles);
|
log.info("Bitmap tiles to load in memory : " + tiles);
|
||||||
maxImgCacheSize = (int) (tiles) ;
|
bitmapTilesCache.setMaxCacheSize((int) (tiles));
|
||||||
|
}
|
||||||
|
|
||||||
|
public BitmapTilesCache getBitmapTilesCache() {
|
||||||
|
return bitmapTilesCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeometryTilesCache getGeometryTilesCache() {
|
||||||
|
return geometryTilesCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MapTileDownloader getMapTileDownloader() {
|
public MapTileDownloader getMapTileDownloader() {
|
||||||
|
@ -261,6 +265,9 @@ public class ResourceManager {
|
||||||
context.getAppPath(".nomedia").createNewFile(); //$NON-NLS-1$
|
context.getAppPath(".nomedia").createNewFile(); //$NON-NLS-1$
|
||||||
} catch( Exception e ) {
|
} catch( Exception e ) {
|
||||||
}
|
}
|
||||||
|
for (TilesCache tilesCache : tilesCacheList) {
|
||||||
|
tilesCache.setDirWithTiles(dirWithTiles);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public java.text.DateFormat getDateFormat() {
|
public java.text.DateFormat getDateFormat() {
|
||||||
|
@ -277,223 +284,61 @@ public class ResourceManager {
|
||||||
|
|
||||||
////////////////////////////////////////////// Working with tiles ////////////////////////////////////////////////
|
////////////////////////////////////////////// Working with tiles ////////////////////////////////////////////////
|
||||||
|
|
||||||
public Bitmap getTileImageForMapAsync(String file, ITileSource map, int x, int y, int zoom, boolean loadFromInternetIfNeeded) {
|
private TilesCache getTilesCache(ITileSource map) {
|
||||||
return getTileImageForMap(file, map, x, y, zoom, loadFromInternetIfNeeded, false, true);
|
for (TilesCache tc : tilesCacheList) {
|
||||||
}
|
if (tc.isTileSourceSupported(map)) {
|
||||||
|
return tc;
|
||||||
|
}
|
||||||
public synchronized Bitmap getTileImageFromCache(String file){
|
}
|
||||||
return cacheOfImages.get(file);
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void putTileInTheCache(String file, Bitmap bmp) {
|
|
||||||
cacheOfImages.put(file, bmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Bitmap getTileImageForMapSync(String file, ITileSource map, int x, int y, int zoom, boolean loadFromInternetIfNeeded) {
|
|
||||||
return getTileImageForMap(file, map, x, y, zoom, loadFromInternetIfNeeded, true, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void tileDownloaded(DownloadRequest request){
|
public synchronized void tileDownloaded(DownloadRequest request){
|
||||||
if(request instanceof TileLoadDownloadRequest){
|
if (request instanceof TileLoadDownloadRequest) {
|
||||||
TileLoadDownloadRequest req = ((TileLoadDownloadRequest) request);
|
TileLoadDownloadRequest req = ((TileLoadDownloadRequest) request);
|
||||||
imagesOnFS.put(req.tileId, Boolean.TRUE);
|
TilesCache cache = getTilesCache(req.tileSource);
|
||||||
/* if(req.fileToSave != null && req.tileSource instanceof SQLiteTileSource){
|
if (cache != null) {
|
||||||
try {
|
cache.tilesOnFS.put(req.tileId, Boolean.TRUE);
|
||||||
((SQLiteTileSource) req.tileSource).insertImage(req.xTile, req.yTile, req.zoom, req.fileToSave);
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.warn("File "+req.fileToSave.getName() + " couldn't be read", e); //$NON-NLS-1$//$NON-NLS-2$
|
|
||||||
}
|
|
||||||
req.fileToSave.delete();
|
|
||||||
String[] l = req.fileToSave.getParentFile().list();
|
|
||||||
if(l == null || l.length == 0){
|
|
||||||
req.fileToSave.getParentFile().delete();
|
|
||||||
l = req.fileToSave.getParentFile().getParentFile().list();
|
|
||||||
if(l == null || l.length == 0){
|
|
||||||
req.fileToSave.getParentFile().getParentFile().delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized boolean tileExistOnFileSystem(String file, ITileSource map, int x, int y, int zoom){
|
|
||||||
if(!imagesOnFS.containsKey(file)){
|
|
||||||
boolean ex = false;
|
|
||||||
if(map instanceof SQLiteTileSource){
|
|
||||||
if(((SQLiteTileSource) map).isLocked()){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ex = ((SQLiteTileSource) map).exists(x, y, zoom);
|
|
||||||
} else {
|
|
||||||
if(file == null){
|
|
||||||
file = calculateTileId(map, x, y, zoom);
|
|
||||||
}
|
|
||||||
ex = new File(dirWithTiles, file).exists();
|
|
||||||
}
|
|
||||||
if (ex) {
|
|
||||||
imagesOnFS.put(file, Boolean.TRUE);
|
|
||||||
} else {
|
|
||||||
imagesOnFS.put(file, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return imagesOnFS.get(file) != null || cacheOfImages.get(file) != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearTileImageForMap(String file, ITileSource map, int x, int y, int zoom){
|
public synchronized boolean tileExistOnFileSystem(String file, ITileSource map, int x, int y, int zoom) {
|
||||||
getTileImageForMap(file, map, x, y, zoom, true, false, true, true);
|
TilesCache cache = getTilesCache(map);
|
||||||
|
return cache != null && !cache.tilesOnFS.containsKey(file)
|
||||||
|
&& cache.tileExistOnFileSystem(file, map, x, y, zoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void clearTileForMap(String file, ITileSource map, int x, int y, int zoom){
|
||||||
* @param file - null could be passed if you do not call very often with that param
|
TilesCache cache = getTilesCache(map);
|
||||||
*/
|
if (cache != null) {
|
||||||
protected Bitmap getTileImageForMap(String file, ITileSource map, int x, int y, int zoom,
|
cache.getTileForMap(file, map, x, y, zoom, true, false, true, true);
|
||||||
boolean loadFromInternetIfNeeded, boolean sync, boolean loadFromFs) {
|
}
|
||||||
return getTileImageForMap(file, map, x, y, zoom, loadFromInternetIfNeeded, sync, loadFromFs, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// introduce cache in order save memory
|
|
||||||
|
|
||||||
protected StringBuilder builder = new StringBuilder(40);
|
|
||||||
protected char[] tileId = new char[120];
|
|
||||||
private GeoidAltitudeCorrection geoidAltitudeCorrection;
|
private GeoidAltitudeCorrection geoidAltitudeCorrection;
|
||||||
private boolean searchAmenitiesInProgress;
|
private boolean searchAmenitiesInProgress;
|
||||||
|
|
||||||
public synchronized String calculateTileId(ITileSource map, int x, int y, int zoom) {
|
public synchronized String calculateTileId(ITileSource map, int x, int y, int zoom) {
|
||||||
builder.setLength(0);
|
TilesCache cache = getTilesCache(map);
|
||||||
if (map == null) {
|
if (cache != null) {
|
||||||
builder.append(IndexConstants.TEMP_SOURCE_TO_LOAD);
|
return cache.calculateTileId(map, x, y, zoom);
|
||||||
} else {
|
|
||||||
builder.append(map.getName());
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (map instanceof SQLiteTileSource) {
|
protected boolean hasRequestedTile(TileLoadDownloadRequest req) {
|
||||||
builder.append('@');
|
TilesCache cache = getTilesCache(req.tileSource);
|
||||||
} else {
|
return cache != null && cache.getRequestedTile(req) != null;
|
||||||
builder.append('/');
|
}
|
||||||
}
|
|
||||||
builder.append(zoom).append('/').append(x).append('/').append(y).
|
public boolean hasTileForMapSync(String file, ITileSource map, int x, int y, int zoom, boolean loadFromInternetIfNeeded) {
|
||||||
append(map == null ? ".jpg" : map.getTileFormat()).append(".tile"); //$NON-NLS-1$ //$NON-NLS-2$
|
TilesCache cache = getTilesCache(map);
|
||||||
return builder.toString();
|
return cache != null && cache.getTileForMapSync(file, map, x, y, zoom, loadFromInternetIfNeeded) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected synchronized Bitmap getTileImageForMap(String tileId, ITileSource map, int x, int y, int zoom,
|
////////////////////////////////////////////// Working with indexes ////////////////////////////////////////////////
|
||||||
boolean loadFromInternetIfNeeded, boolean sync, boolean loadFromFs, boolean deleteBefore) {
|
|
||||||
if (tileId == null) {
|
|
||||||
tileId = calculateTileId(map, x, y, zoom);
|
|
||||||
if(tileId == null){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(deleteBefore){
|
|
||||||
cacheOfImages.remove(tileId);
|
|
||||||
if (map instanceof SQLiteTileSource) {
|
|
||||||
((SQLiteTileSource) map).deleteImage(x, y, zoom);
|
|
||||||
} else {
|
|
||||||
File f = new File(dirWithTiles, tileId);
|
|
||||||
if (f.exists()) {
|
|
||||||
f.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
imagesOnFS.put(tileId, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loadFromFs && cacheOfImages.get(tileId) == null && map != null) {
|
|
||||||
boolean locked = map instanceof SQLiteTileSource && ((SQLiteTileSource) map).isLocked();
|
|
||||||
if(!loadFromInternetIfNeeded && !locked && !tileExistOnFileSystem(tileId, map, x, y, zoom)){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String url = loadFromInternetIfNeeded ? map.getUrlToLoad(x, y, zoom) : null;
|
|
||||||
File toSave = null;
|
|
||||||
if (url != null) {
|
|
||||||
if (map instanceof SQLiteTileSource) {
|
|
||||||
toSave = new File(dirWithTiles, calculateTileId(((SQLiteTileSource) map).getBase(), x, y, zoom));
|
|
||||||
} else {
|
|
||||||
toSave = new File(dirWithTiles, tileId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TileLoadDownloadRequest req = new TileLoadDownloadRequest(dirWithTiles, url, toSave,
|
|
||||||
tileId, map, x, y, zoom, map.getReferer());
|
|
||||||
if(sync){
|
|
||||||
return getRequestedImageTile(req);
|
|
||||||
} else {
|
|
||||||
asyncLoadingThread.requestToLoadImage(req);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cacheOfImages.get(tileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected Bitmap getRequestedImageTile(TileLoadDownloadRequest req){
|
|
||||||
if(req.tileId == null || req.dirWithTiles == null){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Bitmap cacheBmp = cacheOfImages.get(req.tileId);
|
|
||||||
if (cacheBmp != null) {
|
|
||||||
return cacheBmp;
|
|
||||||
}
|
|
||||||
if (cacheOfImages.size() > maxImgCacheSize) {
|
|
||||||
clearTiles();
|
|
||||||
}
|
|
||||||
if (req.dirWithTiles.canRead() && !asyncLoadingThread.isFileCurrentlyDownloaded(req.fileToSave)
|
|
||||||
&& !asyncLoadingThread.isFilePendingToDownload(req.fileToSave)) {
|
|
||||||
long time = System.currentTimeMillis();
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Start loaded file : " + req.tileId + " " + Thread.currentThread().getName()); //$NON-NLS-1$ //$NON-NLS-2$
|
|
||||||
}
|
|
||||||
Bitmap bmp = null;
|
|
||||||
if (req.tileSource instanceof SQLiteTileSource) {
|
|
||||||
try {
|
|
||||||
long[] tm = new long[1];
|
|
||||||
bmp = ((SQLiteTileSource) req.tileSource).getImage(req.xTile, req.yTile, req.zoom, tm);
|
|
||||||
if (tm[0] != 0) {
|
|
||||||
int ts = req.tileSource.getExpirationTimeMillis();
|
|
||||||
if (ts != -1 && req.url != null && time - tm[0] > ts) {
|
|
||||||
asyncLoadingThread.requestToDownload(req);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (OutOfMemoryError e) {
|
|
||||||
log.error("Out of memory error", e); //$NON-NLS-1$
|
|
||||||
clearTiles();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
File en = new File(req.dirWithTiles, req.tileId);
|
|
||||||
if (en.exists()) {
|
|
||||||
try {
|
|
||||||
bmp = BitmapFactory.decodeFile(en.getAbsolutePath());
|
|
||||||
int ts = req.tileSource.getExpirationTimeMillis();
|
|
||||||
if(ts != -1 && req.url != null && time - en.lastModified() > ts) {
|
|
||||||
asyncLoadingThread.requestToDownload(req);
|
|
||||||
}
|
|
||||||
} catch (OutOfMemoryError e) {
|
|
||||||
log.error("Out of memory error", e); //$NON-NLS-1$
|
|
||||||
clearTiles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bmp != null) {
|
|
||||||
cacheOfImages.put(req.tileId, bmp);
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Loaded file : " + req.tileId + " " + -(time - System.currentTimeMillis()) + " ms " + cacheOfImages.size()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cacheOfImages.get(req.tileId) == null && req.url != null) {
|
|
||||||
asyncLoadingThread.requestToDownload(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return cacheOfImages.get(req.tileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////// Working with indexes ////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public List<String> reloadIndexesOnStart(AppInitializer progress, List<String> warnings){
|
public List<String> reloadIndexesOnStart(AppInitializer progress, List<String> warnings){
|
||||||
close();
|
close();
|
||||||
|
@ -1077,7 +922,9 @@ public class ResourceManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void close(){
|
public synchronized void close(){
|
||||||
imagesOnFS.clear();
|
for (TilesCache tc : tilesCacheList) {
|
||||||
|
tc.close();
|
||||||
|
}
|
||||||
indexFileNames.clear();
|
indexFileNames.clear();
|
||||||
basemapFileNames.clear();
|
basemapFileNames.clear();
|
||||||
renderer.clearAllResources();
|
renderer.clearAllResources();
|
||||||
|
@ -1140,7 +987,7 @@ public class ResourceManager {
|
||||||
if (lf != null) {
|
if (lf != null) {
|
||||||
for (File f : lf) {
|
for (File f : lf) {
|
||||||
if (f != null && f.getName().endsWith(IndexConstants.BINARY_MAP_INDEX_EXT)) {
|
if (f != null && f.getName().endsWith(IndexConstants.BINARY_MAP_INDEX_EXT)) {
|
||||||
map.put(f.getName(), AndroidUtils.formatDate(context, f.lastModified())); //$NON-NLS-1$
|
map.put(f.getName(), AndroidUtils.formatDate(context, f.lastModified()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1148,15 +995,17 @@ public class ResourceManager {
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void reloadTilesFromFS(){
|
public synchronized void reloadTilesFromFS() {
|
||||||
imagesOnFS.clear();
|
for (TilesCache tc : tilesCacheList) {
|
||||||
|
tc.tilesOnFS.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// On low memory method ///
|
/// On low memory method ///
|
||||||
public void onLowMemory() {
|
public void onLowMemory() {
|
||||||
log.info("On low memory : cleaning tiles - size = " + cacheOfImages.size()); //$NON-NLS-1$
|
log.info("On low memory");
|
||||||
clearTiles();
|
clearTiles();
|
||||||
for(RegionAddressRepository r : addressMap.values()){
|
for (RegionAddressRepository r : addressMap.values()) {
|
||||||
r.clearCache();
|
r.clearCache();
|
||||||
}
|
}
|
||||||
renderer.clearCache();
|
renderer.clearCache();
|
||||||
|
@ -1172,13 +1021,10 @@ public class ResourceManager {
|
||||||
return context.getRegions();
|
return context.getRegions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected synchronized void clearTiles() {
|
protected synchronized void clearTiles() {
|
||||||
log.info("Cleaning tiles - size = " + cacheOfImages.size()); //$NON-NLS-1$
|
log.info("Cleaning tiles...");
|
||||||
ArrayList<String> list = new ArrayList<String>(cacheOfImages.keySet());
|
for (TilesCache tc : tilesCacheList) {
|
||||||
// remove first images (as we think they are older)
|
tc.clearTiles();
|
||||||
for (int i = 0; i < list.size() / 2; i++) {
|
|
||||||
cacheOfImages.remove(list.get(i));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
234
OsmAnd/src/net/osmand/plus/resources/TilesCache.java
Normal file
234
OsmAnd/src/net/osmand/plus/resources/TilesCache.java
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
package net.osmand.plus.resources;
|
||||||
|
|
||||||
|
import net.osmand.IndexConstants;
|
||||||
|
import net.osmand.PlatformUtil;
|
||||||
|
import net.osmand.map.ITileSource;
|
||||||
|
import net.osmand.plus.SQLiteTileSource;
|
||||||
|
import net.osmand.plus.resources.AsyncLoadingThread.TileLoadDownloadRequest;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public abstract class TilesCache<T> {
|
||||||
|
private AsyncLoadingThread asyncLoadingThread;
|
||||||
|
protected static final Log log = PlatformUtil.getLog(TilesCache.class);
|
||||||
|
|
||||||
|
Map<String, T> cache = new LinkedHashMap<>();
|
||||||
|
Map<String, Boolean> tilesOnFS = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
protected File dirWithTiles;
|
||||||
|
protected int maxCacheSize = 30;
|
||||||
|
|
||||||
|
public TilesCache(AsyncLoadingThread asyncLoadingThread) {
|
||||||
|
this.asyncLoadingThread = asyncLoadingThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected StringBuilder builder = new StringBuilder(40);
|
||||||
|
|
||||||
|
public int getMaxCacheSize() {
|
||||||
|
return maxCacheSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxCacheSize(int maxCacheSize) {
|
||||||
|
this.maxCacheSize = maxCacheSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getDirWithTiles() {
|
||||||
|
return dirWithTiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirWithTiles(File dirWithTiles) {
|
||||||
|
this.dirWithTiles = dirWithTiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean isTileSourceSupported(ITileSource tileSource);
|
||||||
|
|
||||||
|
public synchronized String calculateTileId(ITileSource map, int x, int y, int zoom) {
|
||||||
|
builder.setLength(0);
|
||||||
|
if (map == null) {
|
||||||
|
builder.append(IndexConstants.TEMP_SOURCE_TO_LOAD);
|
||||||
|
} else {
|
||||||
|
builder.append(map.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map instanceof SQLiteTileSource) {
|
||||||
|
builder.append('@');
|
||||||
|
} else {
|
||||||
|
builder.append('/');
|
||||||
|
}
|
||||||
|
builder.append(zoom).append('/').append(x).append('/').append(y).
|
||||||
|
append(map == null ? ".jpg" : map.getTileFormat()).append(".tile"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean tileExistOnFileSystem(String file, ITileSource map, int x, int y, int zoom) {
|
||||||
|
if (!tilesOnFS.containsKey(file)) {
|
||||||
|
boolean ex = false;
|
||||||
|
if (map instanceof SQLiteTileSource){
|
||||||
|
if (((SQLiteTileSource) map).isLocked()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ex = ((SQLiteTileSource) map).exists(x, y, zoom);
|
||||||
|
} else {
|
||||||
|
if(file == null){
|
||||||
|
file = calculateTileId(map, x, y, zoom);
|
||||||
|
}
|
||||||
|
ex = new File(dirWithTiles, file).exists();
|
||||||
|
}
|
||||||
|
if (ex) {
|
||||||
|
tilesOnFS.put(file, Boolean.TRUE);
|
||||||
|
} else {
|
||||||
|
tilesOnFS.put(file, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tilesOnFS.get(file) != null || cache.get(file) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getTileForMapAsync(String file, ITileSource map, int x, int y, int zoom, boolean loadFromInternetIfNeeded) {
|
||||||
|
return getTileForMap(file, map, x, y, zoom, loadFromInternetIfNeeded, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getTileForMapSync(String file, ITileSource map, int x, int y, int zoom, boolean loadFromInternetIfNeeded) {
|
||||||
|
return getTileForMap(file, map, x, y, zoom, loadFromInternetIfNeeded, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param file - null could be passed if you do not call very often with that param
|
||||||
|
*/
|
||||||
|
protected T getTileForMap(String file, ITileSource map, int x, int y, int zoom,
|
||||||
|
boolean loadFromInternetIfNeeded, boolean sync, boolean loadFromFs) {
|
||||||
|
return getTileForMap(file, map, x, y, zoom, loadFromInternetIfNeeded, sync, loadFromFs, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized T getTileForMap(String tileId, ITileSource map, int x, int y, int zoom,
|
||||||
|
boolean loadFromInternetIfNeeded, boolean sync,
|
||||||
|
boolean loadFromFs, boolean deleteBefore) {
|
||||||
|
if (tileId == null) {
|
||||||
|
tileId = calculateTileId(map, x, y, zoom);
|
||||||
|
if(tileId == null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteBefore){
|
||||||
|
cache.remove(tileId);
|
||||||
|
if (map instanceof SQLiteTileSource) {
|
||||||
|
((SQLiteTileSource) map).deleteImage(x, y, zoom);
|
||||||
|
} else {
|
||||||
|
File f = new File(dirWithTiles, tileId);
|
||||||
|
if (f.exists()) {
|
||||||
|
f.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tilesOnFS.put(tileId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadFromFs && cache.get(tileId) == null && map != null) {
|
||||||
|
boolean locked = map instanceof SQLiteTileSource && ((SQLiteTileSource) map).isLocked();
|
||||||
|
if (!loadFromInternetIfNeeded && !locked && !tileExistOnFileSystem(tileId, map, x, y, zoom)){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String url = loadFromInternetIfNeeded ? map.getUrlToLoad(x, y, zoom) : null;
|
||||||
|
File toSave = null;
|
||||||
|
if (url != null) {
|
||||||
|
if (map instanceof SQLiteTileSource) {
|
||||||
|
toSave = new File(dirWithTiles, calculateTileId(((SQLiteTileSource) map).getBase(), x, y, zoom));
|
||||||
|
} else {
|
||||||
|
toSave = new File(dirWithTiles, tileId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TileLoadDownloadRequest req = new TileLoadDownloadRequest(dirWithTiles, url, toSave,
|
||||||
|
tileId, map, x, y, zoom, map.getReferer());
|
||||||
|
if (sync) {
|
||||||
|
return getRequestedTile(req);
|
||||||
|
} else {
|
||||||
|
asyncLoadingThread.requestToLoadTile(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cache.get(tileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected T getRequestedTile(TileLoadDownloadRequest req) {
|
||||||
|
if (req.tileId == null || req.dirWithTiles == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
T cacheObject = cache.get(req.tileId);
|
||||||
|
if (cacheObject != null) {
|
||||||
|
return cacheObject;
|
||||||
|
}
|
||||||
|
if (cache.size() > maxCacheSize) {
|
||||||
|
clearTiles();
|
||||||
|
}
|
||||||
|
if (req.dirWithTiles.canRead() && !asyncLoadingThread.isFileCurrentlyDownloaded(req.fileToSave)
|
||||||
|
&& !asyncLoadingThread.isFilePendingToDownload(req.fileToSave)) {
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Start loaded file : " + req.tileId + " " + Thread.currentThread().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
T tileObject = getTileObject(req);
|
||||||
|
|
||||||
|
if (tileObject != null) {
|
||||||
|
cache.put(req.tileId, tileObject);
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Loaded file : " + req.tileId + " " + -(time - System.currentTimeMillis()) + " ms " + cache.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cache.get(req.tileId) == null && req.url != null) {
|
||||||
|
asyncLoadingThread.requestToDownload(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return cache.get(req.tileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract T getTileObject(TileLoadDownloadRequest req);
|
||||||
|
|
||||||
|
protected void downloadIfExpired(TileLoadDownloadRequest req, long lastModified) {
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
int ts = req.tileSource.getExpirationTimeMillis();
|
||||||
|
if(ts != -1 && req.url != null && time - lastModified > ts) {
|
||||||
|
asyncLoadingThread.requestToDownload(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized void clearTiles() {
|
||||||
|
log.info("Cleaning tiles - size = " + cache.size());
|
||||||
|
List<String> list = new ArrayList<>(cache.keySet());
|
||||||
|
// remove first images (as we think they are older)
|
||||||
|
for (int i = 0; i < list.size() / 2; i++) {
|
||||||
|
cache.remove(list.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized T get(String key) {
|
||||||
|
return cache.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void put(String key, T value) {
|
||||||
|
cache.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized T remove(String key) {
|
||||||
|
return cache.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int size() {
|
||||||
|
return cache.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Set<String> keySet() {
|
||||||
|
return cache.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
tilesOnFS.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,14 @@
|
||||||
package net.osmand.plus.views;
|
package net.osmand.plus.views;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import net.osmand.data.QuadRect;
|
import net.osmand.data.QuadRect;
|
||||||
import net.osmand.data.RotatedTileBox;
|
import net.osmand.data.RotatedTileBox;
|
||||||
import net.osmand.map.ITileSource;
|
import net.osmand.map.ITileSource;
|
||||||
|
@ -12,32 +21,24 @@ import net.osmand.plus.mapillary.MapillaryPlugin;
|
||||||
import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin;
|
import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin;
|
||||||
import net.osmand.plus.resources.ResourceManager;
|
import net.osmand.plus.resources.ResourceManager;
|
||||||
import net.osmand.util.MapUtils;
|
import net.osmand.util.MapUtils;
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Matrix;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.RectF;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
public class MapTileLayer extends BaseMapLayer {
|
public class MapTileLayer extends BaseMapLayer {
|
||||||
|
|
||||||
protected static final int emptyTileDivisor = 16;
|
protected static final int emptyTileDivisor = 16;
|
||||||
public static final int OVERZOOM_IN = 2;
|
public static final int OVERZOOM_IN = 2;
|
||||||
|
|
||||||
private final boolean mainMap;
|
protected final boolean mainMap;
|
||||||
protected ITileSource map = null;
|
protected ITileSource map = null;
|
||||||
protected MapTileAdapter mapTileAdapter = null;
|
protected MapTileAdapter mapTileAdapter = null;
|
||||||
|
|
||||||
Paint paintBitmap;
|
protected Paint paintBitmap;
|
||||||
protected RectF bitmapToDraw = new RectF();
|
protected RectF bitmapToDraw = new RectF();
|
||||||
protected Rect bitmapToZoom = new Rect();
|
protected Rect bitmapToZoom = new Rect();
|
||||||
|
|
||||||
|
|
||||||
protected OsmandMapTileView view;
|
protected OsmandMapTileView view;
|
||||||
protected ResourceManager resourceManager;
|
protected ResourceManager resourceManager;
|
||||||
private OsmandSettings settings;
|
protected OsmandSettings settings;
|
||||||
private boolean visible = true;
|
private boolean visible = true;
|
||||||
|
|
||||||
|
|
||||||
|
@ -175,7 +176,7 @@ public class MapTileLayer extends BaseMapLayer {
|
||||||
boolean imgExist = mgr.tileExistOnFileSystem(ordImgTile, map, tileX, tileY, nzoom);
|
boolean imgExist = mgr.tileExistOnFileSystem(ordImgTile, map, tileX, tileY, nzoom);
|
||||||
boolean originalWillBeLoaded = useInternet && nzoom <= maxLevel;
|
boolean originalWillBeLoaded = useInternet && nzoom <= maxLevel;
|
||||||
if (imgExist || originalWillBeLoaded) {
|
if (imgExist || originalWillBeLoaded) {
|
||||||
bmp = mgr.getTileImageForMapAsync(ordImgTile, map, tileX, tileY, nzoom, useInternet);
|
bmp = mgr.getBitmapTilesCache().getTileForMapAsync(ordImgTile, map, tileX, tileY, nzoom, useInternet);
|
||||||
}
|
}
|
||||||
if (bmp == null) {
|
if (bmp == null) {
|
||||||
int div = 1;
|
int div = 1;
|
||||||
|
@ -188,14 +189,14 @@ public class MapTileLayer extends BaseMapLayer {
|
||||||
div *= 2;
|
div *= 2;
|
||||||
String imgTileId = mgr.calculateTileId(map, tileX / div, tileY / div, nzoom - kzoom);
|
String imgTileId = mgr.calculateTileId(map, tileX / div, tileY / div, nzoom - kzoom);
|
||||||
if (readFromCache) {
|
if (readFromCache) {
|
||||||
bmp = mgr.getTileImageFromCache(imgTileId);
|
bmp = mgr.getBitmapTilesCache().get(imgTileId);
|
||||||
if (bmp != null) {
|
if (bmp != null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (loadIfExists) {
|
} else if (loadIfExists) {
|
||||||
if (mgr.tileExistOnFileSystem(imgTileId, map, tileX / div, tileY / div, nzoom - kzoom)
|
if (mgr.tileExistOnFileSystem(imgTileId, map, tileX / div, tileY / div, nzoom - kzoom)
|
||||||
|| (useInternet && nzoom - kzoom <= maxLevel)) {
|
|| (useInternet && nzoom - kzoom <= maxLevel)) {
|
||||||
bmp = mgr.getTileImageForMapAsync(imgTileId, map, tileX / div, tileY / div, nzoom
|
bmp = mgr.getBitmapTilesCache().getTileForMapAsync(imgTileId, map, tileX / div, tileY / div, nzoom
|
||||||
- kzoom, useInternet);
|
- kzoom, useInternet);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -243,7 +244,7 @@ public class MapTileLayer extends BaseMapLayer {
|
||||||
bitmapToZoom.width(), bitmapToZoom.height(), m, true);
|
bitmapToZoom.width(), bitmapToZoom.height(), m, true);
|
||||||
bitmapToZoom.set(0, 0, tileSize, tileSize);
|
bitmapToZoom.set(0, 0, tileSize, tileSize);
|
||||||
// very expensive that's why put in the cache
|
// very expensive that's why put in the cache
|
||||||
mgr.putTileInTheCache(ordImgTile, sampled);
|
mgr.getBitmapTilesCache().put(ordImgTile, sampled);
|
||||||
canvas.drawBitmap(sampled, bitmapToZoom, bitmapToDraw, paintBitmap);
|
canvas.drawBitmap(sampled, bitmapToZoom, bitmapToDraw, paintBitmap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue