Ticket #17177: 17177.1.patch
File 17177.1.patch, 77.9 KB (added by , 4 years ago) |
---|
-
src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java b/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
a b 61 61 /** A WMS endpoint entry only stores the WMS server info, without layer, which are chosen later by the user. **/ 62 62 WMS_ENDPOINT("wms_endpoint"), 63 63 /** WMTS stores GetCapabilities URL. Does not store any information about the layer **/ 64 WMTS("wmts"); 64 WMTS("wmts"), 65 /** MapBox Vector Tiles entry*/ 66 MVT("mvt"); 65 67 66 68 private final String typeString; 67 69 -
src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java b/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
a b 32 32 import org.openstreetmap.josm.data.cache.CacheEntryAttributes; 33 33 import org.openstreetmap.josm.data.cache.ICachedLoaderListener; 34 34 import org.openstreetmap.josm.data.cache.JCSCachedTileLoaderJob; 35 import org.openstreetmap.josm.data.imagery.vectortile.VectorTile; 35 36 import org.openstreetmap.josm.data.preferences.LongProperty; 36 37 import org.openstreetmap.josm.tools.HttpClient; 37 38 import org.openstreetmap.josm.tools.Logging; … … 295 296 if (content.length > 0) { 296 297 try (ByteArrayInputStream in = new ByteArrayInputStream(content)) { 297 298 tile.loadImage(in); 298 if (tile.getImage() == null) { 299 if (!(tile instanceof VectorTile) && tile.getImage() == null 300 || (tile instanceof VectorTile) && !tile.isLoaded()) { 299 301 String s = new String(content, StandardCharsets.UTF_8); 300 302 Matcher m = SERVICE_EXCEPTION_PATTERN.matcher(s); 301 303 if (m.matches()) { -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Command.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Command.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Command.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 /** 5 * Command integers for Mapbox Vector Tiles 6 * @author Taylor Smock 7 * @since xxx 8 */ 9 public enum Command { 10 /** 11 * For {@link GeometryTypes#POINT}, each {@link #MoveTo} is a new point. 12 * For {@link GeometryTypes#LINESTRING} and {@link GeometryTypes#POLYGON}, each {@link #MoveTo} is a new geometry of the same type. 13 */ 14 MoveTo((byte) 1, (byte) 2), 15 /** 16 * While not explicitly prohibited for {@link GeometryTypes#POINT}, it should be ignored. 17 * For {@link GeometryTypes#LINESTRING} and {@link GeometryTypes#POLYGON}, each {@link #LineTo} extends that geometry. 18 */ 19 LineTo((byte) 2, (byte) 2), 20 /** 21 * This is only explicitly valid for {@link GeometryTypes#POLYGON}. It closes the {@link GeometryTypes#POLYGON}. 22 */ 23 ClosePath((byte) 7, (byte) 0); 24 25 private final byte id; 26 private final byte parameters; 27 28 Command(byte id, byte parameters) { 29 this.id = id; 30 this.parameters = parameters; 31 } 32 33 /** 34 * Get the command id 35 * @return The id 36 */ 37 public byte getId() { 38 return this.id; 39 } 40 41 /** 42 * Get the number of parameters 43 * @return The number of parameters 44 */ 45 public byte getParameterNumber() { 46 return this.parameters; 47 } 48 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/CommandInteger.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/CommandInteger.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/CommandInteger.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 import java.util.Objects; 5 import java.util.stream.Stream; 6 7 /** 8 * An indicator for a command to be executed 9 * @author Taylor Smock 10 * @since xxx 11 */ 12 public class CommandInteger { 13 private final Command type; 14 private final short[] parameters; 15 private int added; 16 17 /** 18 * Create a new command 19 * @param command the command (treated as an unsigned int) 20 */ 21 public CommandInteger(final int command) { 22 // Technically, the int is unsigned, but it is easier to work with the long 23 final long unsigned = Integer.toUnsignedLong(command); 24 this.type = Stream.of(Command.values()).filter(e -> e.getId() == (unsigned & 0x7)).findAny() 25 .orElseThrow(InvalidMapboxVectorTileException::new); 26 // This is safe, since we are shifting right 3 when we converted an int to a long (for unsigned). 27 // So we <i>cannot</i> lose anything. 28 final int operationsInt = (int) (unsigned >> 3); 29 this.parameters = new short[operationsInt * this.type.getParameterNumber()]; 30 } 31 32 /** 33 * Add a parameter 34 * @param parameterInteger The parameter to add (converted to {@link short}). 35 */ 36 public void addParameter(Number parameterInteger) { 37 this.parameters[added++] = parameterInteger.shortValue(); 38 } 39 40 /** 41 * Get the operations for the command 42 * @return The operations 43 */ 44 public short[] getOperations() { 45 return this.parameters; 46 } 47 48 /** 49 * Get the command type 50 * @return the command type 51 */ 52 public Command getType() { 53 return this.type; 54 } 55 56 /** 57 * Get the expected parameter length 58 * @return The expected parameter size 59 */ 60 public boolean hasAllExpectedParameters() { 61 return this.added >= this.parameters.length; 62 } 63 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Feature.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Feature.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Feature.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 import java.io.IOException; 5 import java.text.NumberFormat; 6 import java.util.ArrayList; 7 import java.util.List; 8 9 import org.openstreetmap.josm.data.osm.TagMap; 10 import org.openstreetmap.josm.data.protobuf.ProtoBufPacked; 11 import org.openstreetmap.josm.data.protobuf.ProtoBufParser; 12 import org.openstreetmap.josm.data.protobuf.ProtoBufRecord; 13 import org.openstreetmap.josm.tools.Utils; 14 15 import static org.openstreetmap.josm.tools.I18n.tr; 16 17 /** 18 * A Feature for a {@link Layer} 19 * @author Taylor Smock 20 * @since xxx 21 */ 22 public class Feature { 23 private static final byte ID_FIELD = 1; 24 private static final byte TAG_FIELD = 2; 25 private static final byte GEOMETRY_TYPE_FIELD = 3; 26 private static final byte GEOMETRY_FIELD = 4; 27 /** The geometry of the feature. Required. */ 28 private final List<CommandInteger> geometry = new ArrayList<>(); 29 30 /** The geometry type of the feature. Required. */ 31 private final GeometryTypes geometryType; 32 33 /** The tags of the feature. Optional. */ 34 private TagMap tags; 35 36 /** The id of the feature. Optional. */ 37 // Technically, uint64 38 private final long id; 39 40 /** 41 * Create a new Feature 42 * @param layer The layer the feature is part of (required for tags) 43 * @param record The record to create the feature from 44 * @throws IOException - if an IO error occurs 45 */ 46 public Feature(Layer layer, ProtoBufRecord record) throws IOException { 47 long tId = 0; 48 GeometryTypes geometryTypeTemp = GeometryTypes.UNKNOWN; 49 String key = null; 50 try (ProtoBufParser parser = new ProtoBufParser(record.getBytes())) { 51 while (parser.hasNext()) { 52 try (ProtoBufRecord next = new ProtoBufRecord(parser)) { 53 if (next.getField() == TAG_FIELD) { 54 if (tags == null) { 55 tags = new TagMap(); 56 } 57 // This is packed in v1 and v2 58 ProtoBufPacked packed = new ProtoBufPacked(next.getBytes()); 59 for (Number number : packed.getArray()) { 60 key = parseTagValue(key, layer, number); 61 } 62 } else if (next.getField() == GEOMETRY_FIELD) { 63 // This is packed in v1 and v2 64 ProtoBufPacked packed = new ProtoBufPacked(next.getBytes()); 65 CommandInteger currentCommand = null; 66 for (Number number : packed.getArray()) { 67 if (currentCommand != null && currentCommand.hasAllExpectedParameters()) { 68 currentCommand = null; 69 } 70 if (currentCommand == null) { 71 currentCommand = new CommandInteger(number.intValue()); 72 this.geometry.add(currentCommand); 73 } else { 74 currentCommand.addParameter(ParameterInteger.decode(number.intValue())); 75 } 76 } 77 // TODO fallback to non-packed 78 } else if (next.getField() == GEOMETRY_TYPE_FIELD) { 79 geometryTypeTemp = GeometryTypes.values()[next.asUnsignedVarInt().intValue()]; 80 } else if (next.getField() == ID_FIELD) { 81 tId = next.asUnsignedVarInt().longValue(); 82 } 83 } 84 } 85 } 86 this.id = tId; 87 this.geometryType = geometryTypeTemp; 88 record.close(); 89 } 90 91 /** 92 * Parse a tag value 93 * @param key The current key (or {@code null}, if {@code null}, the returned value will be the new key) 94 * @param layer The layer with key/value information 95 * @param number The number to get the value from 96 * @return The new key (if {@code null}, then a value was parsed and added to tags) 97 */ 98 private String parseTagValue(String key, Layer layer, Number number) { 99 if (key == null) { 100 key = layer.getKey(number.intValue()); 101 } else { 102 Object value = layer.getValue(number.intValue()); 103 if (value instanceof Double || value instanceof Float) { 104 // reset grouping if the instance is a singleton 105 final NumberFormat numberFormat = NumberFormat.getNumberInstance(); 106 final boolean grouping = numberFormat.isGroupingUsed(); 107 try { 108 numberFormat.setGroupingUsed(false); 109 this.tags.put(key, numberFormat.format(value)); 110 } finally { 111 numberFormat.setGroupingUsed(grouping); 112 } 113 } else { 114 this.tags.put(key, Utils.intern(value.toString())); 115 } 116 key = null; 117 } 118 return key; 119 } 120 121 /** 122 * Get the geometry instructions 123 * @return The geometry 124 */ 125 public List<CommandInteger> getGeometry() { 126 return this.geometry; 127 } 128 129 /** 130 * Get the geometry type 131 * @return The {@link GeometryTypes} 132 */ 133 public GeometryTypes getGeometryType() { 134 return this.geometryType; 135 } 136 137 /** 138 * Get the id of the object 139 * @return The unique id in the layer, or 0. 140 */ 141 public long getId() { 142 return this.id; 143 } 144 145 /** 146 * Get the tags 147 * @return A tag map 148 */ 149 public TagMap getTags() { 150 return this.tags; 151 } 152 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Geometry.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Geometry.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Geometry.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.awt.Shape; 7 import java.awt.geom.Ellipse2D; 8 import java.awt.geom.Path2D; 9 import java.util.Collection; 10 import java.util.Collections; 11 import java.util.HashSet; 12 import java.util.List; 13 14 /** 15 * A class to generate geometry for a vector tile 16 * @author Taylor Smock 17 * @since xxx 18 */ 19 public class Geometry { 20 private static final byte CIRCLE_SIZE = 1; 21 final Collection<Shape> shapes = new HashSet<>(); 22 private final Feature feature; 23 24 /** 25 * Create a {@link Geometry} for a {@link Feature} 26 * @param feature the {@link Feature} for the geometry 27 */ 28 public Geometry(final Feature feature) { 29 this.feature = feature; 30 final GeometryTypes geometryType = this.feature.getGeometryType(); 31 final List<CommandInteger> commands = this.feature.getGeometry(); 32 final byte circleSize = CIRCLE_SIZE; 33 if (geometryType == GeometryTypes.POINT) { 34 for (CommandInteger command : commands) { 35 final short[] operations = command.getOperations(); 36 // Each MoveTo command is a new point 37 if (command.getType() == Command.MoveTo && operations.length % 2 == 0) { 38 for (int i = 0; i < operations.length / 2; i++) { 39 // move left/up by 1/2 circleSize, so that the circle is centered 40 shapes.add(new Ellipse2D.Float(operations[2 * i] - circleSize / 2f, operations[2 * i + 1] - circleSize / 2f, circleSize, circleSize)); 41 } 42 } else { 43 throw new IllegalArgumentException(tr("{0} with {1} arguments is not understood", geometryType, operations.length)); 44 } 45 } 46 } else if (geometryType == GeometryTypes.LINESTRING || geometryType == GeometryTypes.POLYGON) { 47 Path2D.Float line = null; 48 for (CommandInteger command : commands) { 49 final short[] operations = command.getOperations(); 50 // Technically, there is no reason why there can be multiple MoveTo operations in one command, but that is undefined behavior 51 if (command.getType() == Command.MoveTo && operations.length == 2) { 52 final double x; 53 final double y; 54 if (line != null) { 55 x = line.getCurrentPoint().getX() + operations[0]; 56 y = line.getCurrentPoint().getY() + operations[1]; 57 } else { 58 x = operations[0]; 59 y = operations[1]; 60 } 61 line = new Path2D.Float(); 62 line.moveTo(x, y); 63 shapes.add(line); 64 } else if (command.getType() == Command.LineTo && operations.length % 2 == 0 && line != null) { 65 for (int i = 0; i < operations.length / 2; i++) { 66 final double x = line.getCurrentPoint().getX() + operations[2 * i]; 67 final double y = line.getCurrentPoint().getY() + operations[2 * i + 1]; 68 line.lineTo(x, y); 69 } 70 } 71 // ClosePath should only be used with Polygon geometry 72 else if (geometryType == GeometryTypes.POLYGON && command.getType() == Command.ClosePath && line != null) { 73 line.closePath(); 74 } else { 75 throw new IllegalArgumentException(tr("{0} with {1} arguments is not understood", geometryType, operations.length)); 76 } 77 } 78 } 79 } 80 81 /** 82 * Get the feature for this geometry 83 * @return The feature 84 */ 85 public Feature getFeature() { 86 return this.feature; 87 } 88 89 /** 90 * Get the shapes to draw this geometry with 91 * @return A collection of shapes 92 */ 93 public Collection<Shape> getShapes() { 94 return Collections.unmodifiableCollection(this.shapes); 95 } 96 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/GeometryTypes.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/GeometryTypes.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/GeometryTypes.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 /** 5 * Geometry types used by Mapbox Vector Tiles 6 * @author Taylor Smock 7 * @since xxx 8 */ 9 public enum GeometryTypes { 10 /** May be ignored */ 11 UNKNOWN((byte) 0), 12 /** May be a point or a multipoint geometry. Uses <i>only</i> {@link Command#MoveTo}. Multiple {@link Command#MoveTo} 13 * indicates that it is a multi-point object. */ 14 POINT((byte) 1), 15 /** May be a line or a multiline geometry. Each line {@link Command#MoveTo} and one or more {@link Command#LineTo}. */ 16 LINESTRING((byte) 2), 17 /** May be a polygon or a multipolygon. Each ring uses a {@link Command#MoveTo}, one or more {@link Command#LineTo}, 18 * and one {@link Command#ClosePath} command. See {@link Ring}s. */ 19 POLYGON((byte) 3); 20 21 private final byte id; 22 GeometryTypes(byte id) { 23 this.id = id; 24 } 25 26 /** 27 * Get the id for the geometry type 28 * @return The id 29 */ 30 public byte getId() { 31 return this.id; 32 } 33 34 /** 35 * Rings used by {@link GeometryTypes#POLYGON} 36 * @author Taylor Smock 37 */ 38 public enum Ring { 39 /** A ring that goes in the clockwise direction */ 40 ExteriorRing, 41 /** A ring that goes in the anti-clockwise direction */ 42 InteriorRing 43 } 44 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/InvalidMapboxVectorTileException.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/InvalidMapboxVectorTileException.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/InvalidMapboxVectorTileException.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 /** 5 * Thrown when a mapbox vector tile does not match specifications. 6 * 7 * @author Taylor Smock 8 * @since xxx 9 */ 10 public class InvalidMapboxVectorTileException extends RuntimeException { 11 /** 12 * Create a default {@link InvalidMapboxVectorTileException}. 13 */ 14 public InvalidMapboxVectorTileException() { 15 super(); 16 } 17 18 /** 19 * Create a new {@link InvalidMapboxVectorTile} exception with a message 20 * @param message The message 21 */ 22 public InvalidMapboxVectorTileException(final String message) { 23 super(message); 24 } 25 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Layer.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Layer.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Layer.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 import static org.openstreetmap.josm.tools.I18n.tr; 4 5 import java.io.IOException; 6 import java.util.ArrayList; 7 import java.util.Arrays; 8 import java.util.Collection; 9 import java.util.Collections; 10 import java.util.HashSet; 11 import java.util.List; 12 import java.util.Map; 13 import java.util.Objects; 14 import java.util.function.Function; 15 import java.util.stream.Collectors; 16 17 import org.openstreetmap.josm.data.protobuf.ProtoBufParser; 18 import org.openstreetmap.josm.data.protobuf.ProtoBufRecord; 19 import org.openstreetmap.josm.data.protobuf.WireType; 20 import org.openstreetmap.josm.tools.Logging; 21 22 /** 23 * A Mapbox Vector Tile Layer 24 * @author Taylor Smock 25 * @since xxx 26 */ 27 public class Layer { 28 private static final class ValueFields<T> { 29 static final ValueFields<String> STRING = new ValueFields<>(1, ProtoBufRecord::asString); 30 static final ValueFields<Float> FLOAT = new ValueFields<>(2, ProtoBufRecord::asFloat); 31 static final ValueFields<Double> DOUBLE = new ValueFields<>(3, ProtoBufRecord::asDouble); 32 static final ValueFields<Number> INT64 = new ValueFields<>(4, ProtoBufRecord::asUnsignedVarInt); 33 // This may have issues if there are actual uint_values (i.e., more than {@link Long#MAX_VALUE}) 34 static final ValueFields<Number> UINT64 = new ValueFields<>(5, ProtoBufRecord::asUnsignedVarInt); 35 static final ValueFields<Number> SINT64 = new ValueFields<>(6, ProtoBufRecord::asSignedVarInt); 36 static final ValueFields<Boolean> BOOL = new ValueFields<>(7, r -> r.asUnsignedVarInt().longValue() != 0); 37 38 public static final Collection<ValueFields<?>> MAPPERS = Arrays.asList(STRING, FLOAT, DOUBLE, INT64, UINT64, SINT64, BOOL); 39 40 private final byte field; 41 private final Function<ProtoBufRecord, T> conversion; 42 private ValueFields(int field, Function<ProtoBufRecord, T> conversion) { 43 this.field = (byte) field; 44 this.conversion = conversion; 45 } 46 47 /** 48 * Get the field identifier for the value 49 * @return The identifier 50 */ 51 public byte getField() { 52 return this.field; 53 } 54 55 /** 56 * Convert a protobuf record to a value 57 * @param protobufRecord The record to convert 58 * @return the converted value 59 */ 60 public T convertValue(ProtoBufRecord protobufRecord) { 61 return this.conversion.apply(protobufRecord); 62 } 63 } 64 65 /** The field value for a layer (in {@link ProtoBufRecord#getField}) */ 66 public static final byte LAYER_FIELD = 3; 67 private static final byte VERSION_FIELD = 15; 68 private static final byte NAME_FIELD = 1; 69 private static final byte FEATURE_FIELD = 2; 70 private static final byte KEY_FIELD = 3; 71 private static final byte VALUE_FIELD = 4; 72 private static final byte EXTENT_FIELD = 5; 73 /** The default extent for a vector tile */ 74 static final int DEFAULT_EXTENT = 4096; 75 private static final byte DEFAULT_VERSION = 1; 76 /** This is <i>technically</i> an integer, but there are currently only two major versions (1, 2). Required. */ 77 private final byte version; 78 /** A unique name for the layer. This <i>must</i> be unique on a per-tile basis. Required. */ 79 private final String name; 80 81 /** The extent of the tile, typically 4096. Required. */ 82 private final int extent; 83 84 /** A list of unique keys. Order is important. Optional. */ 85 private final List<String> keyList = new ArrayList<>(); 86 /** A list of unique values. Order is important. Optional. */ 87 private final List<Object> valueList = new ArrayList<>(); 88 /** The actual features of this layer in this tile */ 89 private final List<Feature> featureCollection; 90 /** The shapes to use to draw this layer */ 91 private final List<Geometry> geometryCollection; 92 93 /** 94 * Create a layer from a collection of records 95 * @param records The records to convert to a layer 96 * @throws IOException - if an IO error occurs 97 */ 98 public Layer(Collection<ProtoBufRecord> records) throws IOException { 99 // Do the unique required fields first 100 Map<Integer, List<ProtoBufRecord>> sorted = records.stream().collect(Collectors.groupingBy(ProtoBufRecord::getField)); 101 this.version = sorted.get((int) VERSION_FIELD).parallelStream().map(ProtoBufRecord::asSignedVarInt).map(Number::byteValue).findFirst().orElse(DEFAULT_VERSION); 102 // Per spec, we cannot continue past this until we have checked the version number 103 if (this.version != 1 && this.version != 2) { 104 throw new IllegalArgumentException(tr("We do not understand version {0} of the vector tile specification", this.version)); 105 } 106 this.name = sorted.getOrDefault((int) NAME_FIELD, Collections.emptyList()).parallelStream().map(ProtoBufRecord::asString).findFirst() 107 .orElseThrow(() -> new IllegalArgumentException(tr("Vector tile layers must have a layer name"))); 108 this.extent = sorted.getOrDefault((int) EXTENT_FIELD, Collections.emptyList()).parallelStream().map(ProtoBufRecord::asSignedVarInt).map(Number::intValue).findAny().orElse(DEFAULT_EXTENT); 109 110 sorted.getOrDefault((int) KEY_FIELD, Collections.emptyList()).parallelStream().map(ProtoBufRecord::asString).forEachOrdered(this.keyList::add); 111 sorted.getOrDefault((int) VALUE_FIELD, Collections.emptyList()).parallelStream().map(ProtoBufRecord::getBytes).map(ProtoBufParser::new).map(parser1 -> {try {return new ProtoBufRecord(parser1);} catch (IOException e) {return null;}}) 112 .filter(Objects::nonNull) 113 .map(value -> ValueFields.MAPPERS.parallelStream() 114 .filter(v -> v.getField() == value.getField()) 115 .map(v -> v.convertValue(value)).findFirst() 116 .orElseThrow(() -> new IllegalArgumentException(tr("Unknown field in vector tile layer value ({0})", value.getField())))) 117 .forEachOrdered(this.valueList::add); 118 Collection<IOException> exceptions = new HashSet<>(0); 119 this.featureCollection = sorted.getOrDefault((int) FEATURE_FIELD, Collections.emptyList()).parallelStream().map(feature -> { 120 try { 121 return new Feature(this, feature); 122 } catch (IOException e) { 123 exceptions.add(e); 124 } 125 return null; 126 }).collect(Collectors.toList()); 127 this.geometryCollection = this.featureCollection.stream().map(Geometry::new).collect(Collectors.toList()); 128 if (!exceptions.isEmpty()) { 129 throw exceptions.iterator().next(); 130 } 131 // Cleanup bytes (for memory) 132 for (ProtoBufRecord record : records) { 133 try { 134 record.close(); 135 } catch (Exception e) { 136 Logging.error(e); 137 } 138 } 139 } 140 141 /** 142 * Create a new layer 143 * @param bytes The bytes that the layer comes from 144 * @throws IOException - if an IO error occurs 145 */ 146 public Layer(byte[] bytes) throws IOException { 147 this(new ProtoBufParser(bytes).allRecords()); 148 } 149 150 /** 151 * Get the extent of the tile 152 * @return The layer extent 153 */ 154 public int getExtent() { 155 return this.extent; 156 } 157 158 /** 159 * Get the feature on this layer 160 * @return the features 161 */ 162 public Collection<Feature> getFeatures() { 163 return Collections.unmodifiableCollection(this.featureCollection); 164 } 165 166 /** 167 * Get the geometry for this layer 168 * @return The geometry 169 */ 170 public Collection<Geometry> getGeometry() { 171 return Collections.unmodifiableCollection(this.geometryCollection); 172 } 173 174 /** 175 * Get a specified key 176 * @param index The index in the key list 177 * @return The actual key 178 */ 179 public String getKey(int index) { 180 return this.keyList.get(index); 181 } 182 183 /** 184 * Get the name of the layer 185 * @return The layer name 186 */ 187 public String getName() { 188 return this.name; 189 } 190 191 /** 192 * Get a specified value 193 * @param index The index in the value list 194 * @return The actual value. This can be a {@link String}, {@link Boolean}, {@link Integer}, or {@link Float} value. 195 */ 196 public Object getValue(int index) { 197 return this.valueList.get(index); 198 } 199 200 /** 201 * Get the MapBox Vector Tile version specification for this layer 202 * @return The version of the MapBox Vector Tile specification 203 */ 204 public byte getVersion() { 205 return this.version; 206 } 207 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapBoxVectorCachedTileLoader.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapBoxVectorCachedTileLoader.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapBoxVectorCachedTileLoader.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 import java.util.concurrent.ThreadPoolExecutor; 5 6 import org.apache.commons.jcs3.access.behavior.ICacheAccess; 7 import org.openstreetmap.gui.jmapviewer.Tile; 8 import org.openstreetmap.gui.jmapviewer.interfaces.CachedTileLoader; 9 import org.openstreetmap.gui.jmapviewer.interfaces.TileJob; 10 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 11 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; 12 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 13 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 14 import org.openstreetmap.josm.data.cache.JCSCachedTileLoaderJob; 15 import org.openstreetmap.josm.data.imagery.TMSCachedTileLoader; 16 import org.openstreetmap.josm.data.imagery.TileJobOptions; 17 import org.openstreetmap.josm.data.preferences.IntegerProperty; 18 import org.openstreetmap.josm.tools.CheckParameterUtil; 19 20 public class MapBoxVectorCachedTileLoader implements TileLoader, CachedTileLoader { 21 protected final ICacheAccess<String, BufferedImageCacheEntry> cache; 22 protected final TileLoaderListener listener; 23 protected final TileJobOptions options; 24 private static final IntegerProperty THREAD_LIMIT = new IntegerProperty("imagery.vector.mvtloader.maxjobs", TMSCachedTileLoader.THREAD_LIMIT.getDefaultValue()); 25 private static final ThreadPoolExecutor DEFAULT_DOWNLOAD_JOB_DISPATCHER = TMSCachedTileLoader.getNewThreadPoolExecutor("MVT-downloader-%d", THREAD_LIMIT.get()); 26 27 /** 28 * Constructor 29 * @param listener called when tile loading has finished 30 * @param cache of the cache 31 * @param options tile job options 32 */ 33 public MapBoxVectorCachedTileLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache, 34 TileJobOptions options) { 35 CheckParameterUtil.ensureParameterNotNull(cache, "cache"); 36 this.cache = cache; 37 this.options = options; 38 this.listener = listener; 39 } 40 41 @Override 42 public void clearCache(TileSource source) { 43 this.cache.remove(source.getName() + ':'); 44 } 45 46 @Override 47 public TileJob createTileLoaderJob(Tile tile) { 48 return new MapBoxVectorCachedTileLoaderJob( 49 listener, 50 tile, 51 cache, 52 options, 53 getDownloadExecutor()); 54 } 55 56 @Override 57 public void cancelOutstandingTasks() { 58 final ThreadPoolExecutor executor = getDownloadExecutor(); 59 executor.getQueue().stream().filter(executor::remove).filter(MapBoxVectorCachedTileLoaderJob.class::isInstance).map(MapBoxVectorCachedTileLoaderJob.class::cast).forEach(JCSCachedTileLoaderJob::handleJobCancellation); 60 } 61 62 @Override 63 public boolean hasOutstandingTasks() { 64 return getDownloadExecutor().getTaskCount() > getDownloadExecutor().getCompletedTaskCount(); 65 } 66 67 private ThreadPoolExecutor getDownloadExecutor() { 68 return DEFAULT_DOWNLOAD_JOB_DISPATCHER; 69 } 70 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapBoxVectorCachedTileLoaderJob.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapBoxVectorCachedTileLoaderJob.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapBoxVectorCachedTileLoaderJob.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 import java.util.concurrent.ThreadPoolExecutor; 5 6 import org.apache.commons.jcs3.access.behavior.ICacheAccess; 7 import org.openstreetmap.gui.jmapviewer.Tile; 8 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; 9 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 10 import org.openstreetmap.josm.data.imagery.TMSCachedTileLoaderJob; 11 import org.openstreetmap.josm.data.imagery.TileJobOptions; 12 13 public class MapBoxVectorCachedTileLoaderJob extends TMSCachedTileLoaderJob { 14 15 public MapBoxVectorCachedTileLoaderJob(TileLoaderListener listener, Tile tile, 16 ICacheAccess<String, BufferedImageCacheEntry> cache, TileJobOptions options, 17 ThreadPoolExecutor downloadExecutor) { 18 super(listener, tile, cache, options, downloadExecutor); 19 } 20 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapboxVectorTileSource.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapboxVectorTileSource.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapboxVectorTileSource.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 import org.openstreetmap.josm.data.imagery.ImageryInfo; 5 import org.openstreetmap.josm.data.imagery.JosmTemplatedTMSTileSource; 6 import org.openstreetmap.josm.data.projection.Projection; 7 8 /** 9 * Tile Source handling for Mapbox Vector Tile sources 10 * @author Taylor Smock 11 * @since xxx 12 */ 13 public class MapboxVectorTileSource extends JosmTemplatedTMSTileSource { 14 public MapboxVectorTileSource(ImageryInfo info, Projection tileProjection) { 15 super(info); 16 //super.initProjection(); 17 } 18 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MVTFile.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MVTFile.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MVTFile.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 import java.util.Arrays; 5 import java.util.List; 6 7 /** 8 * Items that MAY be used to figure out if a file or server response MAY BE a Mapbox Vector Tile 9 * @author Taylor Smock 10 * @since xxx 11 */ 12 public final class MVTFile { 13 /** 14 * Extensions for Mapbox Vector Tiles. 15 * This is a SHOULD, <i>not</i> a MUST. 16 */ 17 public static final List<String> EXTENSION = Arrays.asList("mvt"); 18 19 /** 20 * mimetypes for Mapbox Vector Tiles 21 * This is a SHOULD, <i>not</i> a MUST. 22 */ 23 public static final List<String> MIMETYPE = Arrays.asList("application/vnd.mapbox-vector-tile"); 24 25 /** 26 * The default projection. This is Web Mercator, per specification. 27 */ 28 public static final String DEFAULT_PROJECTION = "EPSG:3857"; 29 30 private MVTFile() { 31 // Hide the constructor 32 } 33 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MVTTile.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MVTTile.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MVTTile.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 import java.awt.BasicStroke; 5 import java.awt.Color; 6 import java.awt.Graphics; 7 import java.awt.Graphics2D; 8 import java.awt.Shape; 9 import java.awt.font.FontRenderContext; 10 import java.awt.geom.AffineTransform; 11 import java.awt.geom.Ellipse2D; 12 import java.awt.geom.Line2D; 13 import java.awt.geom.Path2D; 14 import java.awt.image.BufferedImage; 15 import java.awt.image.ImageObserver; 16 import java.io.IOException; 17 import java.io.InputStream; 18 import java.util.Collection; 19 import java.util.HashSet; 20 import java.util.stream.Collectors; 21 22 import org.openstreetmap.gui.jmapviewer.Tile; 23 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 24 import org.openstreetmap.josm.data.imagery.vectortile.VectorTile; 25 import org.openstreetmap.josm.data.protobuf.ProtoBufParser; 26 import org.openstreetmap.josm.data.protobuf.ProtoBufRecord; 27 28 public class MVTTile extends Tile implements VectorTile { 29 private Collection<Geometry> geometry; 30 private int extent = Layer.DEFAULT_EXTENT; 31 32 public MVTTile(TileSource source, int xtile, int ytile, int zoom) { 33 super(source, xtile, ytile, zoom); 34 } 35 36 @Override 37 public BufferedImage getImage() { 38 return this.image; 39 } 40 41 @Override 42 public void paint(final Graphics g, final int x, final int y) { 43 this.paint(g, x, y, 256, 256); 44 } 45 46 @Override 47 public void paint(Graphics g, int x, int y, int width, int height, ImageObserver observer) { 48 if (!(g instanceof Graphics2D) || this.geometry == null) { 49 if (getImage() != null) { 50 g.drawImage(image, x, y, width, height, observer); 51 } 52 return; 53 } 54 final Graphics2D graphics = (Graphics2D) g; 55 graphics.setColor(Color.GREEN); 56 final AffineTransform original = graphics.getTransform(); 57 try { 58 AffineTransform translation = (AffineTransform) original.clone(); 59 translation.translate(x, y); 60 graphics.setTransform(translation); 61 this.geometry.forEach(shapes -> { 62 for (Shape shape : shapes.getShapes()) { 63 if (shape instanceof Ellipse2D) { 64 graphics.setColor(Color.GREEN); 65 } else if (shape instanceof Path2D) { 66 graphics.setColor(Color.RED); 67 } 68 graphics.draw(shape); 69 } 70 }); 71 } finally { 72 graphics.setTransform(original); 73 } 74 FontRenderContext fontContext = graphics.getFontRenderContext(); 75 graphics.getFontMetrics().getHeight(); 76 graphics.setColor(Color.RED); 77 graphics.drawString("0, 0", 1024, 1024); 78 } 79 80 @Override 81 public void setImage(final BufferedImage image) { 82 this.image = image; 83 } 84 85 @Override 86 public void loadImage(final InputStream inputStream) throws IOException { 87 if (this.image == null || this.image == Tile.LOADING_IMAGE || this.image == Tile.ERROR_IMAGE) { 88 this.initLoading(); 89 ProtoBufParser parser = new ProtoBufParser(inputStream); 90 Collection<ProtoBufRecord> protoBufRecords = parser.allRecords(); 91 Collection<Layer> layers = new HashSet<>(); 92 for (ProtoBufRecord record : protoBufRecords) { 93 if (record.getField() == Layer.LAYER_FIELD) { 94 Layer mvtLayer = new Layer(new ProtoBufParser(record.getBytes()).allRecords()); 95 layers.add(mvtLayer); 96 // Cleanup bytes 97 record.close(); 98 } 99 } 100 // TODO Store layers separately 101 this.geometry = layers.stream().flatMap(layer -> layer.getGeometry().stream()).collect(Collectors.toList()); 102 this.extent = layers.stream().map(Layer::getExtent).max(Integer::compare).orElse(Layer.DEFAULT_EXTENT); 103 BufferedImage bufferedImage = new BufferedImage(this.extent, this.extent, BufferedImage.TYPE_4BYTE_ABGR); 104 Graphics2D graphics = bufferedImage.createGraphics(); 105 this.paint(graphics, 0, 0); 106 107 // TODO figure out a better way to free memory 108 final int maxSize = 256; 109 final BufferedImage resized = new BufferedImage(maxSize, maxSize, bufferedImage.getType()); 110 resized.getGraphics().drawImage(bufferedImage, 0, 0, resized.getWidth(), resized.getHeight(), null); 111 this.image = resized; 112 this.finishLoading(); 113 } 114 } 115 116 @Override 117 public int getExtent() { 118 return this.extent; 119 } 120 121 /** 122 * Returns the X coordinate. 123 * @return tile number on the x axis of this tile 124 */ 125 public int getXtile() { 126 return xtile; 127 } 128 129 /** 130 * Returns the Y coordinate. 131 * @return tile number on the y axis of this tile 132 */ 133 public int getYtile() { 134 return ytile; 135 } 136 137 public int getZoom() { 138 return super.getZoom(); 139 } 140 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/ParameterInteger.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/ParameterInteger.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/ParameterInteger.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 /** 5 * The parameters that follow the {@link CommandInteger}. 6 * @author Taylor Smock 7 * @since xxx 8 */ 9 public class ParameterInteger { 10 private ParameterInteger() { 11 // Hide constructor 12 } 13 14 /** 15 * Get the value for this ParameterInteger 16 * @param value The value to decode 17 * @return The decoded integer value 18 */ 19 public static int decode(int value) { 20 return ((value >> 1) ^ (-(value & 1))); 21 } 22 } -
new file src/org/openstreetmap/josm/data/imagery/vectortile/VectorTile.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/VectorTile.java b/src/org/openstreetmap/josm/data/imagery/vectortile/VectorTile.java new file mode 100644
- + 1 package org.openstreetmap.josm.data.imagery.vectortile; 2 3 import java.awt.Graphics; 4 import java.awt.image.ImageObserver; 5 6 public interface VectorTile { 7 /** 8 * Paints the vector tile on the {@link Graphics} <code>g</code> at the 9 * position <code>x</code>/<code>y</code>. 10 * 11 * @param g the Graphics object 12 * @param x x-coordinate in <code>g</code> 13 * @param y y-coordinate in <code>g</code> 14 */ 15 void paint(final Graphics g, final int x, final int y); 16 17 /** 18 * Paints the vector tile on the {@link Graphics} <code>g</code> at the 19 * position <code>x</code>/<code>y</code>. 20 * @param g the Graphics object 21 * @param x x-coordinate in <code>g</code> 22 * @param y y-coordinate in <code>g</code> 23 * @param width width that tile should have 24 * @param height height that tile should have 25 * @param observer The paint observer. May be {@code null}. 26 */ 27 void paint(Graphics g, int x, int y, int width, int height, ImageObserver observer); 28 29 /** 30 * Get the extent of the tile (in pixels) 31 * @return The tile extent (pixels) 32 */ 33 int getExtent(); 34 } -
new file src/org/openstreetmap/josm/data/protobuf/ProtoBufPacked.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/protobuf/ProtoBufPacked.java b/src/org/openstreetmap/josm/data/protobuf/ProtoBufPacked.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import java.util.ArrayList; 5 import java.util.List; 6 7 /** 8 * Parse packed values (only numerical values) 9 * @author Taylor Smock 10 * @since xxx 11 */ 12 public class ProtoBufPacked { 13 private final byte[] bytes; 14 private int location; 15 private final Number[] numbers; 16 /** 17 * Create a new ProtoBufPacked object 18 * @param bytes The packed bytes 19 */ 20 public ProtoBufPacked(byte[] bytes) { 21 this.location = 0; 22 this.bytes = bytes; 23 List<Number> numbersT = new ArrayList<>(); 24 while (this.location < bytes.length) { 25 numbersT.add(ProtoBufParser.convertByteArray(this.nextVarInt(), ProtoBufParser.VAR_INT_BYTE_SIZE)); 26 } 27 28 this.numbers = new Number[numbersT.size()]; 29 for (int i = 0; i < numbersT.size(); i++) { 30 this.numbers[i] = numbersT.get(i); 31 } 32 } 33 34 /** 35 * The number of expected values 36 * @return The expected values 37 */ 38 public int size() { 39 return this.numbers.length; 40 } 41 42 /** 43 * Get the parsed number array 44 * @return The number array 45 */ 46 public Number[] getArray() { 47 return this.numbers; 48 } 49 50 private byte[] nextVarInt() { 51 List<Byte> byteList = new ArrayList<>(); 52 while ((this.bytes[this.location] & ProtoBufParser.MOST_SIGNIFICANT_BYTE) == ProtoBufParser.MOST_SIGNIFICANT_BYTE) { 53 // Get rid of the leading bit (shift left 1, then shift right 1 unsigned) 54 byteList.add((byte) (this.bytes[this.location++] ^ ProtoBufParser.MOST_SIGNIFICANT_BYTE)); 55 } 56 // The last byte doesn't drop the most significant bit 57 byteList.add(this.bytes[this.location++]); 58 byte[] byteArray = new byte[byteList.size()]; 59 for (int i = 0; i < byteList.size(); i++) { 60 byteArray[i] = byteList.get(i); 61 } 62 63 return byteArray; 64 } 65 } -
new file src/org/openstreetmap/josm/data/protobuf/ProtoBufParser.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/protobuf/ProtoBufParser.java b/src/org/openstreetmap/josm/data/protobuf/ProtoBufParser.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import java.io.BufferedInputStream; 5 import java.io.ByteArrayInputStream; 6 import java.io.IOException; 7 import java.io.InputStream; 8 import java.util.ArrayList; 9 import java.util.Collection; 10 import java.util.List; 11 12 import org.openstreetmap.josm.tools.Logging; 13 14 15 /** 16 * A basic Protobuf parser 17 * @author Taylor Smock 18 * @since xxx 19 */ 20 public class ProtoBufParser implements AutoCloseable { 21 /** 22 * Used to get the most significant byte 23 */ 24 static final byte MOST_SIGNIFICANT_BYTE = (byte) (1 << 7); 25 /** The default byte size (see {@link #VAR_INT_BYTE_SIZE} for var ints) */ 26 public static final byte BYTE_SIZE = 8; 27 /** The byte size for var ints (since the first byte is just an indicator for if the var int is done) */ 28 public static final byte VAR_INT_BYTE_SIZE = BYTE_SIZE - 1; 29 // TODO switch to a better parser 30 private final InputStream inputStream; 31 /** 32 * Create a new parser 33 * @param bytes The bytes to parse 34 */ 35 public ProtoBufParser(byte[] bytes) { 36 this(new ByteArrayInputStream(bytes)); 37 } 38 39 /** 40 * Create a new parser 41 * @param inputStream The InputStream (will be fully read at this time) 42 */ 43 public ProtoBufParser(InputStream inputStream) { 44 if (inputStream.markSupported()) { 45 this.inputStream = inputStream; 46 } else { 47 this.inputStream = new BufferedInputStream(inputStream); 48 } 49 } 50 51 @Override 52 public void close() { 53 try { 54 this.inputStream.close(); 55 } catch (IOException e) { 56 Logging.error(e); 57 } 58 } 59 60 /** 61 * Get the "next" WireType 62 * @return {@link WireType} expected 63 * @throws IOException - if an IO error occurs 64 */ 65 public WireType next() throws IOException { 66 this.inputStream.mark(16); 67 try { 68 return WireType.values()[this.inputStream.read() << 3]; 69 } finally { 70 this.inputStream.reset(); 71 } 72 } 73 74 /** 75 * Get the next byte 76 * @return The next byte 77 * @throws IOException - if an IO error occurs 78 */ 79 public int nextByte() throws IOException { 80 return this.inputStream.read(); 81 } 82 83 /** 84 * Check if there is more data to read 85 * @return {@code true} if there is more data to read 86 * @throws IOException - if an IO error occurs 87 */ 88 public boolean hasNext() throws IOException { 89 return this.inputStream.available() > 0; 90 } 91 92 /** 93 * Get the next var int ({@code WireType#VARINT}) 94 * @return The next var int ({@code int32}, {@code int64}, {@code uint32}, {@code uint64}, {@code bool}, {@code enum}) 95 * @throws IOException - if an IO error occurs 96 */ 97 public byte[] nextVarInt() throws IOException { 98 List<Byte> byteList = new ArrayList<>(); 99 int currentByte = this.nextByte(); 100 while ((byte) (currentByte & MOST_SIGNIFICANT_BYTE) == MOST_SIGNIFICANT_BYTE) { 101 // Get rid of the leading bit (shift left 1, then shift right 1 unsigned) 102 byteList.add((byte) (currentByte ^ MOST_SIGNIFICANT_BYTE)); 103 currentByte = this.nextByte(); 104 } 105 // The last byte doesn't drop the most significant bit 106 byteList.add((byte) currentByte); 107 byte[] byteArray = new byte[byteList.size()]; 108 for (int i = 0; i < byteList.size(); i++) { 109 byteArray[i] = byteList.get(i); 110 } 111 112 return byteArray; 113 } 114 115 /** 116 * Get the next 32 bits ({@link WireType#THIRTY_TWO_BIT}) 117 * @return a byte array of the next 32 bits (4 bytes) 118 * @throws IOException - if an IO error occurs 119 */ 120 public byte[] nextFixed32() throws IOException { 121 // 4 bytes == 32 bits 122 return readNextBytes(4); 123 } 124 125 /** 126 * Get the next 64 bits ({@link WireType#SIXTY_FOUR_BIT}) 127 * @return a byte array of the next 64 bits (8 bytes) 128 * @throws IOException - if an IO error occurs 129 */ 130 public byte[] nextFixed64() throws IOException { 131 // 8 bytes == 64 bits 132 return readNextBytes(8); 133 } 134 135 /** 136 * Read an arbitrary number of bytes 137 * @param size The number of bytes to read 138 * @return a byte array of the specified size, filled with bytes read (unsigned) 139 * @throws IOException - if an IO error occurs 140 */ 141 private byte[] readNextBytes(int size) throws IOException { 142 byte[] bytesRead = new byte[size]; 143 for (int i = 0; i < bytesRead.length; i++) { 144 bytesRead[i] = (byte) this.nextByte(); 145 } 146 return bytesRead; 147 } 148 149 /** 150 * Get the next delimited message ({@link WireType#LENGTH_DELIMITED}) 151 * @return The next length delimited message 152 * @throws IOException - if an IO error occurs 153 */ 154 public byte[] nextLengthDelimited() throws IOException { 155 int length = convertByteArray(this.nextVarInt(), VAR_INT_BYTE_SIZE).intValue(); 156 return readNextBytes(length); 157 } 158 159 /** 160 * Convert a byte array to a number (little endian) 161 * @param bytes The bytes to convert 162 * @param byteSize The size of the byte. For var ints, this is 7, for other ints, this is 8. 163 * @return An appropriate {@link Number} class. 164 */ 165 public static Number convertByteArray(byte[] bytes, byte byteSize) { 166 long number = 0; 167 for (int i = 0; i < bytes.length; i++) { 168 // Need to convert to uint64 in order to avoid bit operation from filling in 1's and overflow issues 169 number += Byte.toUnsignedLong(bytes[i]) << byteSize * i; 170 } 171 return convertLong(number); 172 } 173 174 /** 175 * Convert a long to an appropriate {@link Number} class 176 * @param number The long to convert 177 * @return A {@link Number} 178 */ 179 public static Number convertLong(long number) { 180 // TODO deal with booleans 181 if (number <= Byte.MAX_VALUE && number >= Byte.MIN_VALUE) { 182 return (byte) number; 183 } else if (number <= Short.MAX_VALUE && number >= Short.MIN_VALUE) { 184 return (short) number; 185 } else if (number <= Integer.MAX_VALUE && number >= Integer.MIN_VALUE) { 186 return (int) number; 187 } 188 return number; 189 } 190 191 /** 192 * Read all records 193 * @return A collection of all records 194 * @throws IOException - if an IO error occurs 195 */ 196 public Collection<ProtoBufRecord> allRecords() throws IOException { 197 Collection<ProtoBufRecord> records = new ArrayList<>(); 198 while (this.hasNext()) { 199 records.add(new ProtoBufRecord(this)); 200 } 201 return records; 202 } 203 } -
new file src/org/openstreetmap/josm/data/protobuf/ProtoBufRecord.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/protobuf/ProtoBufRecord.java b/src/org/openstreetmap/josm/data/protobuf/ProtoBufRecord.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 import static org.openstreetmap.josm.tools.I18n.tr; 4 5 import java.io.IOException; 6 import java.nio.charset.StandardCharsets; 7 import java.util.stream.Stream; 8 9 import org.openstreetmap.josm.tools.Utils; 10 11 /** 12 * A protobuf record, storing the {@link WireType}, the parsed field number, and the bytes for it. 13 * @author Taylor Smock 14 * @since xxx 15 */ 16 public class ProtoBufRecord implements AutoCloseable { 17 private static final byte[] EMPTY_BYTES = {}; 18 private final WireType type; 19 private final int field; 20 private byte[] bytes; 21 22 /** 23 * Create a new Protobuf record 24 * @param parser The parser to use to create the record 25 * @throws IOException - if an IO error occurs 26 */ 27 public ProtoBufRecord(ProtoBufParser parser) throws IOException { 28 Number number = ProtoBufParser.convertByteArray(parser.nextVarInt(), ProtoBufParser.VAR_INT_BYTE_SIZE); 29 // I don't foresee having field numbers > {@code Integer#MAX_VALUE >> 3} 30 this.field = (int) number.longValue() >> 3; 31 // 7 is 111 (so last three bits) 32 byte wireType = (byte) (number.longValue() & 7); 33 this.type = Stream.of(WireType.values()).filter(wType -> wType.getTypeRepresentation() == wireType).findFirst().orElse(WireType.UNKNOWN); 34 35 if (this.type == WireType.VARINT) { 36 this.bytes = parser.nextVarInt(); 37 } else if (this.type == WireType.SIXTY_FOUR_BIT) { 38 this.bytes = parser.nextFixed64(); 39 } else if (this.type == WireType.THIRTY_TWO_BIT) { 40 this.bytes = parser.nextFixed32(); 41 } else if (this.type == WireType.LENGTH_DELIMITED) { 42 this.bytes = parser.nextLengthDelimited(); 43 // START_GROUP and END_GROUP are currently used by Mapbox Vector Tiles 44 } else if (this.type == WireType.START_GROUP || this.type == WireType.END_GROUP) { 45 this.bytes = EMPTY_BYTES; 46 } else { 47 throw new IllegalArgumentException(tr("Unknown type: {0} for field {1}", wireType, this.field)); 48 } 49 } 50 51 /** 52 * Get the field value 53 * @return The field value 54 */ 55 public int getField() { 56 return this.field; 57 } 58 59 /** 60 * Get the WireType of the data 61 * @return The {@link WireType} of the data 62 */ 63 public WireType getType() { 64 return this.type; 65 } 66 67 /** 68 * Get the raw bytes for this record 69 * @return The bytes 70 */ 71 public byte[] getBytes() { 72 return this.bytes; 73 } 74 75 /** 76 * Get the var int ({@code WireType#VARINT}) 77 * @return The var int ({@code int32}, {@code int64}, {@code uint32}, {@code uint64}, {@code bool}, {@code enum}) 78 */ 79 public Number asUnsignedVarInt() { 80 return ProtoBufParser.convertByteArray(this.bytes, ProtoBufParser.VAR_INT_BYTE_SIZE); 81 } 82 83 /** 84 * Get the signed var int ({@code WireType#VARINT}). 85 * These are specially encoded so that they take up less space. 86 * 87 * @return The signed var int ({@code sint32} or {@code sint64}) 88 */ 89 public Number asSignedVarInt() { 90 Number signed = this.asUnsignedVarInt(); 91 if (signed instanceof Long) { 92 long value = ((Long) signed).longValue(); 93 return Long.valueOf((value << 1) ^ (value >> 63)); 94 } 95 int value = signed.intValue(); 96 long number = (value << 1) ^ (value >> 31); 97 return ProtoBufParser.convertLong(number); 98 } 99 100 /** 101 * Get as a double ({@link WireType#SIXTY_FOUR_BIT}) 102 * @return the double 103 */ 104 public double asDouble() { 105 long doubleNumber = ProtoBufParser.convertByteArray(asFixed64(), ProtoBufParser.BYTE_SIZE).longValue(); 106 return Double.longBitsToDouble(doubleNumber); 107 } 108 109 /** 110 * Get as a float ({@link WireType#THIRTY_TWO_BIT}) 111 * @return the float 112 */ 113 public float asFloat() { 114 int floatNumber = ProtoBufParser.convertByteArray(asFixed32(), ProtoBufParser.BYTE_SIZE).intValue(); 115 return Float.intBitsToFloat(floatNumber); 116 } 117 118 /** 119 * Get as a string ({@link WireType#LENGTH_DELIMITED}) 120 * @return The string (encoded as {@link StandardCharsets#UTF_8}) 121 */ 122 public String asString() { 123 return Utils.intern(new String(this.bytes, StandardCharsets.UTF_8)); 124 } 125 126 /** 127 * Get as 32 bits ({@link WireType#THIRTY_TWO_BIT}) 128 * @return a byte array of the 32 bits (4 bytes) 129 */ 130 public byte[] asFixed32() { 131 // TODO verify, or just assume? 132 // 4 bytes == 32 bits 133 return this.bytes; 134 } 135 136 /** 137 * Get as 64 bits ({@link WireType#SIXTY_FOUR_BIT}) 138 * @return a byte array of the 64 bits (8 bytes) 139 */ 140 public byte[] asFixed64() { 141 // TODO verify, or just assume? 142 // 8 bytes == 64 bits 143 return this.bytes; 144 } 145 146 @Override 147 public void close() { 148 this.bytes = null; 149 } 150 } -
new file src/org/openstreetmap/josm/data/protobuf/WireType.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/protobuf/WireType.java b/src/org/openstreetmap/josm/data/protobuf/WireType.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 /** 5 * The WireTypes 6 * @author Taylor Smock 7 * @since xxx 8 */ 9 public enum WireType { 10 /** int32, int64, uint32, uint64, sing32, sint64, bool, enum */ 11 VARINT(0), 12 /** fixed64, sfixed64, double */ 13 SIXTY_FOUR_BIT(1), 14 /** string, bytes, embedded messages, packed repeated fields */ 15 LENGTH_DELIMITED(2), 16 /** 17 * start groups 18 * @deprecated Unknown reason. Deprecated since at least 2012. 19 */ 20 @Deprecated 21 START_GROUP(3), 22 /** 23 * end groups 24 * @deprecated Unknown reason. Deprecated since at least 2012. 25 */ 26 @Deprecated 27 END_GROUP(4), 28 /** fixed32, sfixed32, float */ 29 THIRTY_TWO_BIT(5), 30 31 /** For unknown WireTypes */ 32 UNKNOWN(Byte.MAX_VALUE); 33 34 private final byte type; 35 WireType(int value) { 36 this.type = (byte) value; 37 } 38 39 /** 40 * Get the type representation (byte form) 41 * @return The wire type byte representation 42 */ 43 public byte getTypeRepresentation() { 44 return this.type; 45 } 46 } -
new file src/org/openstreetmap/josm/gui/layer/imagery/MVTLayer.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/layer/imagery/MVTLayer.java b/src/org/openstreetmap/josm/gui/layer/imagery/MVTLayer.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.layer.imagery; 3 4 import java.util.Collection; 5 import java.util.Collections; 6 7 import org.openstreetmap.gui.jmapviewer.Tile; 8 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 9 import org.openstreetmap.josm.data.imagery.ImageryInfo; 10 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MVTFile; 11 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MVTTile; 12 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MapBoxVectorCachedTileLoader; 13 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MapboxVectorTileSource; 14 import org.openstreetmap.josm.data.projection.Projections; 15 import org.openstreetmap.josm.gui.layer.AbstractCachedTileSourceLayer; 16 17 public class MVTLayer extends AbstractCachedTileSourceLayer<MapboxVectorTileSource> { 18 private static final String CACHE_REGION_NAME = "MVT"; 19 20 public MVTLayer(ImageryInfo info) { 21 super(info); 22 } 23 24 @Override 25 protected Class<? extends TileLoader> getTileLoaderClass() { 26 return MapBoxVectorCachedTileLoader.class; 27 } 28 29 @Override 30 protected String getCacheName() { 31 return CACHE_REGION_NAME; 32 } 33 34 @Override 35 public Collection<String> getNativeProjections() { 36 // MapBox Vector Tiles <i>specifically</i> only support EPSG:3857 ("it is exclusively geared towards square pixel tiles in {link to EPSG:3857}"). 37 return Collections.singleton(MVTFile.DEFAULT_PROJECTION); 38 } 39 40 @Override 41 protected MapboxVectorTileSource getTileSource() { 42 MapboxVectorTileSource source = new MapboxVectorTileSource(this.info, Projections.getProjectionByCode(MVTFile.DEFAULT_PROJECTION)); 43 this.info.setAttribution(source); 44 return source; 45 } 46 47 @Override 48 public Tile createTile(MapboxVectorTileSource source, int x, int y, int zoom) { 49 return new MVTTile(source, x, y, zoom); 50 } 51 52 } -
src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java b/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
a b 87 87 import org.openstreetmap.josm.data.imagery.OffsetBookmark; 88 88 import org.openstreetmap.josm.data.imagery.TMSCachedTileLoader; 89 89 import org.openstreetmap.josm.data.imagery.TileLoaderFactory; 90 import org.openstreetmap.josm.data.imagery.vectortile.VectorTile; 90 91 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 91 92 import org.openstreetmap.josm.data.preferences.BooleanProperty; 92 93 import org.openstreetmap.josm.data.preferences.IntegerProperty; … … 868 869 if (coordinateConverter.requiresReprojection()) { 869 870 tile = new ReprojectionTile(tileSource, x, y, zoom); 870 871 } else { 871 tile = newTile(tileSource, x, y, zoom);872 tile = createTile(tileSource, x, y, zoom); 872 873 } 873 874 tileCache.addTile(tile); 874 875 } … … 1006 1007 g.setClip(oldClip); 1007 1008 } 1008 1009 } 1010 1011 /** 1012 * Draw a vector tile on screen. 1013 * @param g the Graphics2D 1014 * @param tile the vector tile 1015 * @param anchorImage tile anchor in image coordinates 1016 * @param anchorScreen tile anchor in screen coordinates 1017 * @param clip clipping region in screen coordinates (can be null) 1018 */ 1019 private void drawVectorTileInside(Graphics2D g, VectorTile tile, TileAnchor anchorImage, TileAnchor anchorScreen, Shape clip) { 1020 AffineTransform imageToScreen = anchorImage.convert(anchorScreen); 1021 Point2D screen0 = imageToScreen.transform(new Point2D.Double(0, 0), null); 1022 Point2D screen1 = imageToScreen.transform(new Point2D.Double( 1023 tile.getExtent(), tile.getExtent()), null); 1024 1025 Shape oldClip = null; 1026 if (clip != null) { 1027 oldClip = g.getClip(); 1028 g.clip(clip); 1029 } 1030 tile.paint(g, (int) Math.round(screen0.getX()), (int) Math.round(screen0.getY()), 1031 (int) Math.round(screen1.getX()) - (int) Math.round(screen0.getX()), 1032 (int) Math.round(screen1.getY()) - (int) Math.round(screen0.getY()), this); 1033 if (clip != null) { 1034 g.setClip(oldClip); 1035 } 1036 } 1009 1037 1010 1038 private List<Tile> paintTileImages(Graphics2D g, TileSet ts) { 1011 1039 Object paintMutex = new Object(); … … 1021 1049 img = getLoadedTileImage(tile); 1022 1050 anchorImage = getAnchor(tile, img); 1023 1051 } 1024 if (img == null || anchorImage == null ) {1052 if (img == null || anchorImage == null || (tile instanceof VectorTile && !tile.isLoaded())) { 1025 1053 miss = true; 1026 1054 } 1027 1055 } … … 1030 1058 return; 1031 1059 } 1032 1060 1033 img = applyImageProcessors(img); 1061 if (img != null) { 1062 img = applyImageProcessors(img); 1063 } 1034 1064 1035 1065 TileAnchor anchorScreen = coordinateConverter.getScreenAnchorForTile(tile); 1036 1066 synchronized (paintMutex) { 1037 1067 //cannot paint in parallel 1038 drawImageInside(g, img, anchorImage, anchorScreen, null); 1068 if (tile instanceof VectorTile) { 1069 drawVectorTileInside(g, (VectorTile) tile, anchorImage, anchorScreen, null); 1070 } else { 1071 drawImageInside(g, img, anchorImage, anchorScreen, null); 1072 } 1039 1073 } 1040 1074 MapView mapView = MainApplication.getMap().mapView; 1041 1075 if (tile instanceof ReprojectionTile && ((ReprojectionTile) tile).needsUpdate(mapView.getScale())) { … … 1830 1864 1831 1865 for (int x = minX; x <= maxX; x++) { 1832 1866 for (int y = minY; y <= maxY; y++) { 1833 requestedTiles.add( newTile(tileSource, x, y, currentZoomLevel));1867 requestedTiles.add(createTile(tileSource, x, y, currentZoomLevel)); 1834 1868 } 1835 1869 } 1836 1870 } … … 1929 1963 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save WMS file"), WMSLayerImporter.FILE_FILTER); 1930 1964 } 1931 1965 1966 /** 1967 * Create a new tile. Added to allow use of custom {@link Tile} objects. 1968 * 1969 * @param source Tile source 1970 * @param x X coordinate 1971 * @param y Y coordinate 1972 * @param zoom Zoom level 1973 * @return The new {@link Tile} 1974 * @since xxx 1975 */ 1976 public Tile createTile(T source, int x, int y, int zoom) { 1977 return new Tile(source, x, y, zoom); 1978 } 1979 1932 1980 @Override 1933 1981 public synchronized void destroy() { 1934 1982 super.destroy(); -
src/org/openstreetmap/josm/gui/layer/ImageryLayer.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java b/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java
a b 37 37 import org.openstreetmap.josm.gui.MapView; 38 38 import org.openstreetmap.josm.gui.MenuScroller; 39 39 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings; 40 import org.openstreetmap.josm.gui.layer.imagery.MVTLayer; 40 41 import org.openstreetmap.josm.gui.widgets.UrlLabel; 41 42 import org.openstreetmap.josm.tools.GBC; 42 43 import org.openstreetmap.josm.tools.ImageProcessor; … … 168 169 case BING: 169 170 case SCANEX: 170 171 return new TMSLayer(info); 172 case MVT: 173 return new MVTLayer(info); 171 174 default: 172 175 throw new AssertionError(tr("Unsupported imagery type: {0}", info.getImageryType())); 173 176 } -
src/org/openstreetmap/josm/gui/preferences/imagery/ImageryProvidersPanel.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/preferences/imagery/ImageryProvidersPanel.java b/src/org/openstreetmap/josm/gui/preferences/imagery/ImageryProvidersPanel.java
a b 312 312 activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.WMS)); 313 313 activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.TMS)); 314 314 activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.WMTS)); 315 activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.MVT)); 315 316 activeToolbar.add(remove); 316 317 activePanel.add(activeToolbar, BorderLayout.EAST); 317 318 add(activePanel, GBC.eol().fill(GridBagConstraints.BOTH).weight(2.0, 0.4).insets(5, 0, 0, 5)); … … 439 440 break; 440 441 case WMTS: 441 442 icon = /* ICON(dialogs/) */ "add_wmts"; 443 break; 444 case MVT: 445 icon = /* ICON(dialogs/) */ "add_wmts"; // TODO replace 442 446 break; 443 447 default: 444 448 break; … … 459 463 break; 460 464 case WMTS: 461 465 p = new AddWMTSLayerPanel(); 466 break; 467 case MVT: 468 p = new AddMVTLayerPanel(); 462 469 break; 463 470 default: 464 471 throw new IllegalStateException("Type " + type + " not supported"); -
new file test/unit/org/openstreetmap/josm/data/protobuf/ProtoBufTest.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/test/unit/org/openstreetmap/josm/data/protobuf/ProtoBufTest.java b/test/unit/org/openstreetmap/josm/data/protobuf/ProtoBufTest.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.fail; 6 7 import java.io.File; 8 import java.io.IOException; 9 import java.io.InputStream; 10 import java.nio.file.Paths; 11 import java.text.MessageFormat; 12 import java.util.ArrayList; 13 import java.util.Collection; 14 import java.util.List; 15 16 import org.junit.jupiter.api.Test; 17 import org.openstreetmap.josm.TestUtils; 18 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.Feature; 19 import org.openstreetmap.josm.data.imagery.vectortile.mapbox.Layer; 20 import org.openstreetmap.josm.io.Compression; 21 22 /** 23 * Test class for {@link ProtoBufParser} and {@link ProtoBufRecord} 24 * @author Taylor Smock 25 * @since xxx 26 */ 27 class ProtoBufTest { 28 /** 29 * Test simple message. 30 * Check that a simple message is readable 31 * @throws IOException - if an IO error occurs 32 */ 33 @Test 34 void testSimpleMessage() throws IOException { 35 ProtoBufParser parser = new ProtoBufParser(new byte[] {(byte) 0x08, (byte) 0x96, (byte) 0x01}); 36 ProtoBufRecord record = new ProtoBufRecord(parser); 37 assertEquals(WireType.VARINT, record.getType()); 38 assertEquals(150, record.asUnsignedVarInt().intValue()); 39 } 40 /** 41 * Test reading tile from Mapillary ( 14/3251/6258 ) 42 * @throws IOException if there is a problem reading the file 43 */ 44 @Test 45 void testRead_14_3251_6258() throws IOException { 46 File vectorTile = Paths.get(TestUtils.getTestDataRoot(), "pbf", "6258.mvt").toFile(); 47 InputStream inputStream = Compression.getUncompressedFileInputStream(vectorTile); 48 Collection<ProtoBufRecord> records = new ProtoBufParser(inputStream).allRecords(); 49 assertEquals(2, records.size()); 50 List<Layer> layers = new ArrayList<>(); 51 for (ProtoBufRecord record : records) { 52 if (record.getField() == Layer.LAYER_FIELD) { 53 layers.add(new Layer(record.getBytes())); 54 } else { 55 fail(MessageFormat.format("Invalid field {0}", record.getField())); 56 } 57 } 58 Layer mapillarySequences = layers.get(0); 59 Layer mapillaryPictures = layers.get(1); 60 assertEquals("mapillary-sequences", mapillarySequences.getName()); 61 assertEquals("mapillary-images", mapillaryPictures.getName()); 62 assertEquals(8192, mapillarySequences.getExtent()); 63 assertEquals(8192, mapillaryPictures.getExtent()); 64 65 assertEquals(1, mapillarySequences.getFeatures().stream().filter(feature -> feature.getId() == 241083111).count()); 66 Feature testSequence = mapillarySequences.getFeatures().stream().filter(feature -> feature.getId() == 241083111).findAny().orElse(null); 67 assertEquals("jgxkXqVFM4jepMG3vP5Q9A", testSequence.getTags().get("key")); 68 assertEquals("C15Ul6qVMfQFlzRcmQCLcA", testSequence.getTags().get("ikey")); 69 assertEquals("x0hTY8cakpy0m3ui1GaG1A", testSequence.getTags().get("userkey")); 70 assertEquals(Long.valueOf(1565196718638L), Long.valueOf(testSequence.getTags().get("captured_at"))); 71 assertEquals(0, Integer.parseInt(testSequence.getTags().get("pano"))); 72 } 73 }