Ticket #17177: 17177.2.patch
File 17177.2.patch, 82.9 KB (added by , 4 years ago) |
---|
-
resources/images/dialogs/add_mvt.svg
1 <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 <svg 3 xmlns:dc="http://purl.org/dc/elements/1.1/" 4 xmlns:cc="http://creativecommons.org/ns#" 5 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 6 xmlns:svg="http://www.w3.org/2000/svg" 7 xmlns="http://www.w3.org/2000/svg" 8 xmlns:xlink="http://www.w3.org/1999/xlink" 9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 11 width="24" 12 height="24" 13 viewBox="0 0 24 24" 14 id="svg2" 15 version="1.1" 16 inkscape:version="1.0.1 (c497b03c, 2020-09-10)" 17 sodipodi:docname="add_mvt.svg"> 18 <defs 19 id="defs4"> 20 <linearGradient 21 gradientTransform="translate(4)" 22 gradientUnits="userSpaceOnUse" 23 y2="1049.3622" 24 x2="12" 25 y1="1041.3622" 26 x1="4" 27 id="linearGradient868" 28 xlink:href="#linearGradient866" 29 inkscape:collect="always" /> 30 <linearGradient 31 id="linearGradient866" 32 inkscape:collect="always"> 33 <stop 34 id="stop862" 35 offset="0" 36 style="stop-color:#dfdfdf;stop-opacity:1" /> 37 <stop 38 id="stop864" 39 offset="1" 40 style="stop-color:#949593;stop-opacity:1" /> 41 </linearGradient> 42 </defs> 43 <sodipodi:namedview 44 id="base" 45 pagecolor="#ffffff" 46 bordercolor="#666666" 47 borderopacity="1.0" 48 inkscape:pageopacity="0" 49 inkscape:pageshadow="2" 50 inkscape:zoom="45.254834" 51 inkscape:cx="11.376506" 52 inkscape:cy="17.057298" 53 inkscape:document-units="px" 54 inkscape:current-layer="layer1" 55 showgrid="true" 56 units="px" 57 inkscape:window-width="1920" 58 inkscape:window-height="955" 59 inkscape:window-x="0" 60 inkscape:window-y="23" 61 inkscape:window-maximized="1" 62 viewbox-height="16" 63 inkscape:document-rotation="0"> 64 <inkscape:grid 65 type="xygrid" 66 id="grid4136" 67 originx="0" 68 originy="0" 69 spacingx="1" 70 spacingy="1" /> 71 </sodipodi:namedview> 72 <metadata 73 id="metadata7"> 74 <rdf:RDF> 75 <cc:Work 76 rdf:about=""> 77 <dc:format>image/svg+xml</dc:format> 78 <dc:type 79 rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 80 <dc:title></dc:title> 81 <cc:license 82 rdf:resource="http://creativecommons.org/publicdomain/zero/1.0/" /> 83 </cc:Work> 84 <cc:License 85 rdf:about="http://creativecommons.org/publicdomain/zero/1.0/"> 86 <cc:permits 87 rdf:resource="http://creativecommons.org/ns#Reproduction" /> 88 <cc:permits 89 rdf:resource="http://creativecommons.org/ns#Distribution" /> 90 <cc:permits 91 rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> 92 </cc:License> 93 </rdf:RDF> 94 </metadata> 95 <g 96 inkscape:label="Layer 1" 97 inkscape:groupmode="layer" 98 id="layer1" 99 transform="translate(0,-1037.3622)"> 100 <rect 101 ry="0.48361239" 102 y="1043.8622" 103 x="5.5" 104 height="3" 105 width="13" 106 id="rect833" 107 style="opacity:1;fill:#c1c2c0;fill-opacity:1;fill-rule:nonzero;stroke:#555753;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.839909;stroke-opacity:1;paint-order:normal" /> 108 <rect 109 transform="rotate(-90)" 110 ry="0.48361239" 111 y="10.5" 112 x="-1051.8622" 113 height="3" 114 width="13" 115 id="rect833-5" 116 style="opacity:1;fill:#c1c2c0;fill-opacity:1;fill-rule:nonzero;stroke:#555753;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.839909;stroke-opacity:1;paint-order:normal" /> 117 <path 118 inkscape:connector-curvature="0" 119 id="path852" 120 d="M 6.0000001,1044.3622 H 11 v -5 h 2 v 5 h 5 v 2 h -5 v 5 h -2 v -5 H 6.0000001 Z" 121 style="fill:url(#linearGradient868);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> 122 <path 123 style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" 124 d="m 4.5,1060.3625 v -7.5948 l 2,4.3971 2,-4.3971 v 7.5948" 125 id="path894" 126 sodipodi:nodetypes="ccccc" /> 127 <path 128 style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 129 d="m 17.5,1060.3622 v -8" 130 id="path896" /> 131 <path 132 style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" 133 d="m 15,1052.8622 h 5" 134 id="path898" /> 135 <text 136 xml:space="preserve" 137 style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:9.3042px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.894202;stroke-miterlimit:4;stroke-dasharray:none" 138 x="10.59868" 139 y="898.41876" 140 id="text854" 141 transform="scale(0.84728029,1.180247)"><tspan 142 sodipodi:role="line" 143 id="tspan852" 144 x="10.59868" 145 y="898.41876" 146 style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:9.3042px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill-rule:nonzero;stroke-width:0.894202;stroke-miterlimit:4;stroke-dasharray:none">V</tspan></text> 147 </g> 148 </svg> -
src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
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
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()) { -
src/org/openstreetmap/josm/data/imagery/vectortile/VectorTile.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile; 3 4 import java.awt.Graphics; 5 import java.awt.image.ImageObserver; 6 7 /** 8 * An interface that is used to draw vector tiles, instead of using images 9 * @author Taylor Smock 10 * @since xxx 11 */ 12 public interface VectorTile { 13 /** 14 * Paints the vector tile on the {@link Graphics} <code>g</code> at the 15 * position <code>x</code>/<code>y</code>. 16 * 17 * @param g the Graphics object 18 * @param x x-coordinate in <code>g</code> 19 * @param y y-coordinate in <code>g</code> 20 */ 21 void paint(Graphics g, int x, int y); 22 23 /** 24 * Paints the vector tile on the {@link Graphics} <code>g</code> at the 25 * position <code>x</code>/<code>y</code>. 26 * @param g the Graphics object 27 * @param x x-coordinate in <code>g</code> 28 * @param y y-coordinate in <code>g</code> 29 * @param width width that tile should have 30 * @param height height that tile should have 31 * @param observer The paint observer. May be {@code null}. 32 * @param zoom The current zoom level 33 */ 34 void paint(Graphics g, int x, int y, int width, int height, int zoom, ImageObserver observer); 35 36 /** 37 * Get the extent of the tile (in pixels) 38 * @return The tile extent (pixels) 39 */ 40 int getExtent(); 41 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Command.java
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 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/CommandInteger.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 import java.util.stream.Stream; 5 6 /** 7 * An indicator for a command to be executed 8 * @author Taylor Smock 9 * @since xxx 10 */ 11 public class CommandInteger { 12 private final Command type; 13 private final short[] parameters; 14 private int added; 15 16 /** 17 * Create a new command 18 * @param command the command (treated as an unsigned int) 19 */ 20 public CommandInteger(final int command) { 21 // Technically, the int is unsigned, but it is easier to work with the long 22 final long unsigned = Integer.toUnsignedLong(command); 23 this.type = Stream.of(Command.values()).filter(e -> e.getId() == (unsigned & 0x7)).findAny() 24 .orElseThrow(InvalidMapboxVectorTileException::new); 25 // This is safe, since we are shifting right 3 when we converted an int to a long (for unsigned). 26 // So we <i>cannot</i> lose anything. 27 final int operationsInt = (int) (unsigned >> 3); 28 this.parameters = new short[operationsInt * this.type.getParameterNumber()]; 29 } 30 31 /** 32 * Add a parameter 33 * @param parameterInteger The parameter to add (converted to {@link short}). 34 */ 35 public void addParameter(Number parameterInteger) { 36 this.parameters[added++] = parameterInteger.shortValue(); 37 } 38 39 /** 40 * Get the operations for the command 41 * @return The operations 42 */ 43 public short[] getOperations() { 44 return this.parameters; 45 } 46 47 /** 48 * Get the command type 49 * @return the command type 50 */ 51 public Command getType() { 52 return this.type; 53 } 54 55 /** 56 * Get the expected parameter length 57 * @return The expected parameter size 58 */ 59 public boolean hasAllExpectedParameters() { 60 return this.added >= this.parameters.length; 61 } 62 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Feature.java
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 /** 16 * A Feature for a {@link Layer} 17 * @author Taylor Smock 18 * @since xxx 19 */ 20 public class Feature { 21 private static final byte ID_FIELD = 1; 22 private static final byte TAG_FIELD = 2; 23 private static final byte GEOMETRY_TYPE_FIELD = 3; 24 private static final byte GEOMETRY_FIELD = 4; 25 /** The geometry of the feature. Required. */ 26 private final List<CommandInteger> geometry = new ArrayList<>(); 27 28 /** The geometry type of the feature. Required. */ 29 private final GeometryTypes geometryType; 30 31 /** The tags of the feature. Optional. */ 32 private TagMap tags; 33 34 /** The id of the feature. Optional. */ 35 // Technically, uint64 36 private final long id; 37 38 /** 39 * Create a new Feature 40 * @param layer The layer the feature is part of (required for tags) 41 * @param record The record to create the feature from 42 * @throws IOException - if an IO error occurs 43 */ 44 public Feature(Layer layer, ProtoBufRecord record) throws IOException { 45 long tId = 0; 46 GeometryTypes geometryTypeTemp = GeometryTypes.UNKNOWN; 47 String key = null; 48 try (ProtoBufParser parser = new ProtoBufParser(record.getBytes())) { 49 while (parser.hasNext()) { 50 try (ProtoBufRecord next = new ProtoBufRecord(parser)) { 51 if (next.getField() == TAG_FIELD) { 52 if (tags == null) { 53 tags = new TagMap(); 54 } 55 // This is packed in v1 and v2 56 ProtoBufPacked packed = new ProtoBufPacked(next.getBytes()); 57 for (Number number : packed.getArray()) { 58 key = parseTagValue(key, layer, number); 59 } 60 } else if (next.getField() == GEOMETRY_FIELD) { 61 // This is packed in v1 and v2 62 ProtoBufPacked packed = new ProtoBufPacked(next.getBytes()); 63 CommandInteger currentCommand = null; 64 for (Number number : packed.getArray()) { 65 if (currentCommand != null && currentCommand.hasAllExpectedParameters()) { 66 currentCommand = null; 67 } 68 if (currentCommand == null) { 69 currentCommand = new CommandInteger(number.intValue()); 70 this.geometry.add(currentCommand); 71 } else { 72 currentCommand.addParameter(ParameterInteger.decode(number.intValue())); 73 } 74 } 75 // TODO fallback to non-packed 76 } else if (next.getField() == GEOMETRY_TYPE_FIELD) { 77 geometryTypeTemp = GeometryTypes.values()[next.asUnsignedVarInt().intValue()]; 78 } else if (next.getField() == ID_FIELD) { 79 tId = next.asUnsignedVarInt().longValue(); 80 } 81 } 82 } 83 } 84 this.id = tId; 85 this.geometryType = geometryTypeTemp; 86 record.close(); 87 } 88 89 /** 90 * Parse a tag value 91 * @param key The current key (or {@code null}, if {@code null}, the returned value will be the new key) 92 * @param layer The layer with key/value information 93 * @param number The number to get the value from 94 * @return The new key (if {@code null}, then a value was parsed and added to tags) 95 */ 96 private String parseTagValue(String key, Layer layer, Number number) { 97 if (key == null) { 98 key = layer.getKey(number.intValue()); 99 } else { 100 Object value = layer.getValue(number.intValue()); 101 if (value instanceof Double || value instanceof Float) { 102 // reset grouping if the instance is a singleton 103 final NumberFormat numberFormat = NumberFormat.getNumberInstance(); 104 final boolean grouping = numberFormat.isGroupingUsed(); 105 try { 106 numberFormat.setGroupingUsed(false); 107 this.tags.put(key, numberFormat.format(value)); 108 } finally { 109 numberFormat.setGroupingUsed(grouping); 110 } 111 } else { 112 this.tags.put(key, Utils.intern(value.toString())); 113 } 114 key = null; 115 } 116 return key; 117 } 118 119 /** 120 * Get the geometry instructions 121 * @return The geometry 122 */ 123 public List<CommandInteger> getGeometry() { 124 return this.geometry; 125 } 126 127 /** 128 * Get the geometry type 129 * @return The {@link GeometryTypes} 130 */ 131 public GeometryTypes getGeometryType() { 132 return this.geometryType; 133 } 134 135 /** 136 * Get the id of the object 137 * @return The unique id in the layer, or 0. 138 */ 139 public long getId() { 140 return this.id; 141 } 142 143 /** 144 * Get the tags 145 * @return A tag map 146 */ 147 public TagMap getTags() { 148 return this.tags; 149 } 150 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Geometry.java
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.Area; 8 import java.awt.geom.Ellipse2D; 9 import java.awt.geom.Path2D; 10 import java.util.Collection; 11 import java.util.Collections; 12 import java.util.HashSet; 13 import java.util.List; 14 15 /** 16 * A class to generate geometry for a vector tile 17 * @author Taylor Smock 18 * @since xxx 19 */ 20 public class Geometry { 21 private static final byte CIRCLE_SIZE = 1; 22 final Collection<Shape> shapes = new HashSet<>(); 23 private final Feature feature; 24 25 /** 26 * Create a {@link Geometry} for a {@link Feature} 27 * @param feature the {@link Feature} for the geometry 28 */ 29 public Geometry(final Feature feature) { 30 this.feature = feature; 31 final GeometryTypes geometryType = this.feature.getGeometryType(); 32 final List<CommandInteger> commands = this.feature.getGeometry(); 33 final byte circleSize = CIRCLE_SIZE; 34 if (geometryType == GeometryTypes.POINT) { 35 for (CommandInteger command : commands) { 36 final short[] operations = command.getOperations(); 37 // Each MoveTo command is a new point 38 if (command.getType() == Command.MoveTo && operations.length % 2 == 0) { 39 for (int i = 0; i < operations.length / 2; i++) { 40 // move left/up by 1/2 circleSize, so that the circle is centered 41 shapes.add(new Ellipse2D.Float(operations[2 * i] - circleSize / 2f, 42 operations[2 * i + 1] - circleSize / 2f, circleSize, circleSize)); 43 } 44 } else { 45 throw new IllegalArgumentException(tr("{0} with {1} arguments is not understood", geometryType, operations.length)); 46 } 47 } 48 } else if (geometryType == GeometryTypes.LINESTRING || geometryType == GeometryTypes.POLYGON) { 49 Path2D.Float line = null; 50 for (CommandInteger command : commands) { 51 final short[] operations = command.getOperations(); 52 // Technically, there is no reason why there can be multiple MoveTo operations in one command, but that is undefined behavior 53 if (command.getType() == Command.MoveTo && operations.length == 2) { 54 final double x; 55 final double y; 56 if (line != null) { 57 x = line.getCurrentPoint().getX() + operations[0]; 58 y = line.getCurrentPoint().getY() + operations[1]; 59 } else { 60 x = operations[0]; 61 y = operations[1]; 62 } 63 line = new Path2D.Float(); 64 line.moveTo(x, y); 65 shapes.add(line); 66 } else if (command.getType() == Command.LineTo && operations.length % 2 == 0 && line != null) { 67 for (int i = 0; i < operations.length / 2; i++) { 68 final double x = line.getCurrentPoint().getX() + operations[2 * i]; 69 final double y = line.getCurrentPoint().getY() + operations[2 * i + 1]; 70 line.lineTo(x, y); 71 } 72 // ClosePath should only be used with Polygon geometry 73 } else if (geometryType == GeometryTypes.POLYGON && command.getType() == Command.ClosePath && line != null) { 74 line.closePath(); 75 shapes.remove(line); 76 shapes.add(new Area(line)); 77 } else { 78 throw new IllegalArgumentException(tr("{0} with {1} arguments is not understood", geometryType, operations.length)); 79 } 80 } 81 } 82 } 83 84 /** 85 * Get the feature for this geometry 86 * @return The feature 87 */ 88 public Feature getFeature() { 89 return this.feature; 90 } 91 92 /** 93 * Get the shapes to draw this geometry with 94 * @return A collection of shapes 95 */ 96 public Collection<Shape> getShapes() { 97 return Collections.unmodifiableCollection(this.shapes); 98 } 99 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/GeometryTypes.java
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 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/InvalidMapboxVectorTileException.java
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 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Layer.java
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.tools.Logging; 20 21 /** 22 * A Mapbox Vector Tile Layer 23 * @author Taylor Smock 24 * @since xxx 25 */ 26 public class Layer { 27 private static final class ValueFields<T> { 28 static final ValueFields<String> STRING = new ValueFields<>(1, ProtoBufRecord::asString); 29 static final ValueFields<Float> FLOAT = new ValueFields<>(2, ProtoBufRecord::asFloat); 30 static final ValueFields<Double> DOUBLE = new ValueFields<>(3, ProtoBufRecord::asDouble); 31 static final ValueFields<Number> INT64 = new ValueFields<>(4, ProtoBufRecord::asUnsignedVarInt); 32 // This may have issues if there are actual uint_values (i.e., more than {@link Long#MAX_VALUE}) 33 static final ValueFields<Number> UINT64 = new ValueFields<>(5, ProtoBufRecord::asUnsignedVarInt); 34 static final ValueFields<Number> SINT64 = new ValueFields<>(6, ProtoBufRecord::asSignedVarInt); 35 static final ValueFields<Boolean> BOOL = new ValueFields<>(7, r -> r.asUnsignedVarInt().longValue() != 0); 36 37 public static final Collection<ValueFields<?>> MAPPERS = Arrays.asList(STRING, FLOAT, DOUBLE, INT64, UINT64, SINT64, BOOL); 38 39 private final byte field; 40 private final Function<ProtoBufRecord, T> conversion; 41 private ValueFields(int field, Function<ProtoBufRecord, T> conversion) { 42 this.field = (byte) field; 43 this.conversion = conversion; 44 } 45 46 /** 47 * Get the field identifier for the value 48 * @return The identifier 49 */ 50 public byte getField() { 51 return this.field; 52 } 53 54 /** 55 * Convert a protobuf record to a value 56 * @param protobufRecord The record to convert 57 * @return the converted value 58 */ 59 public T convertValue(ProtoBufRecord protobufRecord) { 60 return this.conversion.apply(protobufRecord); 61 } 62 } 63 64 /** The field value for a layer (in {@link ProtoBufRecord#getField}) */ 65 public static final byte LAYER_FIELD = 3; 66 private static final byte VERSION_FIELD = 15; 67 private static final byte NAME_FIELD = 1; 68 private static final byte FEATURE_FIELD = 2; 69 private static final byte KEY_FIELD = 3; 70 private static final byte VALUE_FIELD = 4; 71 private static final byte EXTENT_FIELD = 5; 72 /** The default extent for a vector tile */ 73 static final int DEFAULT_EXTENT = 4096; 74 private static final byte DEFAULT_VERSION = 1; 75 /** This is <i>technically</i> an integer, but there are currently only two major versions (1, 2). Required. */ 76 private final byte version; 77 /** A unique name for the layer. This <i>must</i> be unique on a per-tile basis. Required. */ 78 private final String name; 79 80 /** The extent of the tile, typically 4096. Required. */ 81 private final int extent; 82 83 /** A list of unique keys. Order is important. Optional. */ 84 private final List<String> keyList = new ArrayList<>(); 85 /** A list of unique values. Order is important. Optional. */ 86 private final List<Object> valueList = new ArrayList<>(); 87 /** The actual features of this layer in this tile */ 88 private final List<Feature> featureCollection; 89 /** The shapes to use to draw this layer */ 90 private final List<Geometry> geometryCollection; 91 92 /** 93 * Create a layer from a collection of records 94 * @param records The records to convert to a layer 95 * @throws IOException - if an IO error occurs 96 */ 97 public Layer(Collection<ProtoBufRecord> records) throws IOException { 98 // Do the unique required fields first 99 Map<Integer, List<ProtoBufRecord>> sorted = records.stream().collect(Collectors.groupingBy(ProtoBufRecord::getField)); 100 this.version = sorted.get((int) VERSION_FIELD).parallelStream().map(ProtoBufRecord::asUnsignedVarInt).map(Number::byteValue) 101 .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) 109 .map(Number::intValue).findAny().orElse(DEFAULT_EXTENT); 110 111 sorted.getOrDefault((int) KEY_FIELD, Collections.emptyList()).parallelStream().map(ProtoBufRecord::asString) 112 .forEachOrdered(this.keyList::add); 113 sorted.getOrDefault((int) VALUE_FIELD, Collections.emptyList()).parallelStream().map(ProtoBufRecord::getBytes) 114 .map(ProtoBufParser::new).map(parser1 -> { 115 try { 116 return new ProtoBufRecord(parser1); 117 } catch (IOException e) { 118 return null; 119 } 120 }) 121 .filter(Objects::nonNull) 122 .map(value -> ValueFields.MAPPERS.parallelStream() 123 .filter(v -> v.getField() == value.getField()) 124 .map(v -> v.convertValue(value)).findFirst() 125 .orElseThrow(() -> new IllegalArgumentException(tr("Unknown field in vector tile layer value ({0})", value.getField())))) 126 .forEachOrdered(this.valueList::add); 127 Collection<IOException> exceptions = new HashSet<>(0); 128 this.featureCollection = sorted.getOrDefault((int) FEATURE_FIELD, Collections.emptyList()).parallelStream().map(feature -> { 129 try { 130 return new Feature(this, feature); 131 } catch (IOException e) { 132 exceptions.add(e); 133 } 134 return null; 135 }).collect(Collectors.toList()); 136 this.geometryCollection = this.featureCollection.stream().map(Geometry::new).collect(Collectors.toList()); 137 if (!exceptions.isEmpty()) { 138 throw exceptions.iterator().next(); 139 } 140 // Cleanup bytes (for memory) 141 for (ProtoBufRecord record : records) { 142 try { 143 record.close(); 144 } catch (Exception e) { 145 Logging.error(e); 146 } 147 } 148 } 149 150 /** 151 * Create a new layer 152 * @param bytes The bytes that the layer comes from 153 * @throws IOException - if an IO error occurs 154 */ 155 public Layer(byte[] bytes) throws IOException { 156 this(new ProtoBufParser(bytes).allRecords()); 157 } 158 159 /** 160 * Get the extent of the tile 161 * @return The layer extent 162 */ 163 public int getExtent() { 164 return this.extent; 165 } 166 167 /** 168 * Get the feature on this layer 169 * @return the features 170 */ 171 public Collection<Feature> getFeatures() { 172 return Collections.unmodifiableCollection(this.featureCollection); 173 } 174 175 /** 176 * Get the geometry for this layer 177 * @return The geometry 178 */ 179 public Collection<Geometry> getGeometry() { 180 return Collections.unmodifiableCollection(this.geometryCollection); 181 } 182 183 /** 184 * Get a specified key 185 * @param index The index in the key list 186 * @return The actual key 187 */ 188 public String getKey(int index) { 189 return this.keyList.get(index); 190 } 191 192 /** 193 * Get the name of the layer 194 * @return The layer name 195 */ 196 public String getName() { 197 return this.name; 198 } 199 200 /** 201 * Get a specified value 202 * @param index The index in the value list 203 * @return The actual value. This can be a {@link String}, {@link Boolean}, {@link Integer}, or {@link Float} value. 204 */ 205 public Object getValue(int index) { 206 return this.valueList.get(index); 207 } 208 209 /** 210 * Get the MapBox Vector Tile version specification for this layer 211 * @return The version of the MapBox Vector Tile specification 212 */ 213 public byte getVersion() { 214 return this.version; 215 } 216 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MVTFile.java
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 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MVTTile.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 3 4 import java.awt.Color; 5 import java.awt.Graphics; 6 import java.awt.Graphics2D; 7 import java.awt.Shape; 8 import java.awt.Stroke; 9 import java.awt.geom.AffineTransform; 10 import java.awt.geom.Area; 11 import java.awt.geom.Ellipse2D; 12 import java.awt.geom.Path2D; 13 import java.awt.image.BufferedImage; 14 import java.awt.image.ImageObserver; 15 import java.io.IOException; 16 import java.io.InputStream; 17 import java.util.Collection; 18 import java.util.HashSet; 19 import java.util.function.Function; 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 /** 29 * A class for MapBox Vector Tiles 30 * @author Taylor Smock 31 * @since xxx 32 */ 33 public class MVTTile extends Tile implements VectorTile { 34 private Collection<Geometry> geometry; 35 private int extent = Layer.DEFAULT_EXTENT; 36 37 public MVTTile(TileSource source, int xtile, int ytile, int zoom) { 38 super(source, xtile, ytile, zoom); 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, int zoom, 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 originalTransform = graphics.getTransform(); 57 final Stroke originalStroke = graphics.getStroke(); 58 try { 59 graphics.translate(x, y); 60 // TODO figure out HiDPI (maybe GuiSizesHelper?) 61 // 131072 seems to be the magic number for my screens. This needs to be investigated more. 62 final double scale = width / (double) 131072; 63 // The scaleTransform is separate to avoid wide lines at high zoom (e.g., when vector tiles go to z14, but 64 // we are currently at z20, the graphics.scale function makes everything big. Unfortunately, this creates 65 // a new shape object. 66 final Function<Shape, Shape> scaleShape; 67 if (scale > 1) { 68 final AffineTransform scaleTransform = AffineTransform.getScaleInstance(scale, scale); 69 scaleShape = scaleTransform::createTransformedShape; 70 } else { 71 scaleShape = Function.identity(); 72 graphics.scale(scale, scale); 73 } 74 final Color transparentYellow = new Color(Color.YELLOW.getRed(), Color.YELLOW.getGreen(), Color.YELLOW.getBlue(), 120); 75 this.geometry.forEach(shapes -> { 76 for (Shape shape : shapes.getShapes()) { 77 final Shape scaledShape = scaleShape.apply(shape); 78 if (shape instanceof Ellipse2D) { 79 graphics.setColor(Color.GREEN); 80 } else if (shape instanceof Path2D) { 81 graphics.setColor(Color.RED); 82 } else if (shape instanceof Area) { 83 graphics.setColor(transparentYellow); 84 graphics.fill(scaledShape); 85 graphics.setColor(Color.YELLOW); 86 } 87 graphics.draw(scaledShape); 88 } 89 }); 90 } finally { 91 graphics.setTransform(originalTransform); 92 graphics.setStroke(originalStroke); 93 } 94 graphics.setColor(Color.RED); 95 graphics.drawString("0, 0", 1024, 1024); 96 } 97 98 @Override 99 public void loadImage(final InputStream inputStream) throws IOException { 100 if (this.image == null || this.image == Tile.LOADING_IMAGE || this.image == Tile.ERROR_IMAGE) { 101 this.initLoading(); 102 ProtoBufParser parser = new ProtoBufParser(inputStream); 103 Collection<ProtoBufRecord> protoBufRecords = parser.allRecords(); 104 Collection<Layer> layers = new HashSet<>(); 105 for (ProtoBufRecord record : protoBufRecords) { 106 if (record.getField() == Layer.LAYER_FIELD) { 107 Layer mvtLayer = new Layer(new ProtoBufParser(record.getBytes()).allRecords()); 108 layers.add(mvtLayer); 109 // Cleanup bytes 110 record.close(); 111 } 112 } 113 // TODO Store layers separately 114 this.geometry = layers.stream().flatMap(layer -> layer.getGeometry().stream()).collect(Collectors.toList()); 115 this.extent = layers.stream().map(Layer::getExtent).max(Integer::compare).orElse(Layer.DEFAULT_EXTENT); 116 BufferedImage bufferedImage = new BufferedImage(this.extent, this.extent, BufferedImage.TYPE_4BYTE_ABGR); 117 Graphics2D graphics = bufferedImage.createGraphics(); 118 this.paint(graphics, 0, 0); 119 120 // TODO figure out a better way to free memory 121 final int maxSize = 256; 122 final BufferedImage resized = new BufferedImage(maxSize, maxSize, bufferedImage.getType()); 123 resized.getGraphics().drawImage(bufferedImage, 0, 0, resized.getWidth(), resized.getHeight(), null); 124 this.image = resized; 125 this.finishLoading(); 126 } 127 } 128 129 @Override 130 public int getExtent() { 131 return this.extent; 132 } 133 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapBoxVectorCachedTileLoader.java
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 /** 21 * A TileLoader class for MVT tiles 22 * @author Taylor Smock 23 * @since xxx 24 */ 25 public class MapBoxVectorCachedTileLoader implements TileLoader, CachedTileLoader { 26 protected final ICacheAccess<String, BufferedImageCacheEntry> cache; 27 protected final TileLoaderListener listener; 28 protected final TileJobOptions options; 29 private static final IntegerProperty THREAD_LIMIT = 30 new IntegerProperty("imagery.vector.mvtloader.maxjobs", TMSCachedTileLoader.THREAD_LIMIT.getDefaultValue()); 31 private static final ThreadPoolExecutor DEFAULT_DOWNLOAD_JOB_DISPATCHER = 32 TMSCachedTileLoader.getNewThreadPoolExecutor("MVT-downloader-%d", THREAD_LIMIT.get()); 33 34 /** 35 * Constructor 36 * @param listener called when tile loading has finished 37 * @param cache of the cache 38 * @param options tile job options 39 */ 40 public MapBoxVectorCachedTileLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache, 41 TileJobOptions options) { 42 CheckParameterUtil.ensureParameterNotNull(cache, "cache"); 43 this.cache = cache; 44 this.options = options; 45 this.listener = listener; 46 } 47 48 @Override 49 public void clearCache(TileSource source) { 50 this.cache.remove(source.getName() + ':'); 51 } 52 53 @Override 54 public TileJob createTileLoaderJob(Tile tile) { 55 return new MapBoxVectorCachedTileLoaderJob( 56 listener, 57 tile, 58 cache, 59 options, 60 getDownloadExecutor()); 61 } 62 63 @Override 64 public void cancelOutstandingTasks() { 65 final ThreadPoolExecutor executor = getDownloadExecutor(); 66 executor.getQueue().stream().filter(executor::remove).filter(MapBoxVectorCachedTileLoaderJob.class::isInstance) 67 .map(MapBoxVectorCachedTileLoaderJob.class::cast).forEach(JCSCachedTileLoaderJob::handleJobCancellation); 68 } 69 70 @Override 71 public boolean hasOutstandingTasks() { 72 return getDownloadExecutor().getTaskCount() > getDownloadExecutor().getCompletedTaskCount(); 73 } 74 75 private ThreadPoolExecutor getDownloadExecutor() { 76 return DEFAULT_DOWNLOAD_JOB_DISPATCHER; 77 } 78 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapBoxVectorCachedTileLoaderJob.java
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 /** 14 * Bridge to JCS cache for MVT tiles 15 * @author Taylor Smock 16 * @since xxx 17 */ 18 public class MapBoxVectorCachedTileLoaderJob extends TMSCachedTileLoaderJob { 19 20 public MapBoxVectorCachedTileLoaderJob(TileLoaderListener listener, Tile tile, 21 ICacheAccess<String, BufferedImageCacheEntry> cache, TileJobOptions options, 22 ThreadPoolExecutor downloadExecutor) { 23 super(listener, tile, cache, options, downloadExecutor); 24 } 25 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapboxVectorTileSource.java
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) { 15 super(info); 16 } 17 } -
src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/ParameterInteger.java
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 final class ParameterInteger { 10 private ParameterInteger() { 11 // Hide constructor 12 } 13 14 /** 15 * Get the value for this ParameterInteger 16 * @param value The zig-zag and delta encoded 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 } -
src/org/openstreetmap/josm/data/protobuf/ProtoBufPacked.java
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 } -
src/org/openstreetmap/josm/data/protobuf/ProtoBufParser.java
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 * A basic Protobuf parser 16 * @author Taylor Smock 17 * @since xxx 18 */ 19 public class ProtoBufParser implements AutoCloseable { 20 /** 21 * Used to get the most significant byte 22 */ 23 static final byte MOST_SIGNIFICANT_BYTE = (byte) (1 << 7); 24 /** The default byte size (see {@link #VAR_INT_BYTE_SIZE} for var ints) */ 25 public static final byte BYTE_SIZE = 8; 26 /** The byte size for var ints (since the first byte is just an indicator for if the var int is done) */ 27 public static final byte VAR_INT_BYTE_SIZE = BYTE_SIZE - 1; 28 // TODO switch to a better parser 29 private final InputStream inputStream; 30 /** 31 * Create a new parser 32 * @param bytes The bytes to parse 33 */ 34 public ProtoBufParser(byte[] bytes) { 35 this(new ByteArrayInputStream(bytes)); 36 } 37 38 /** 39 * Create a new parser 40 * @param inputStream The InputStream (will be fully read at this time) 41 */ 42 public ProtoBufParser(InputStream inputStream) { 43 if (inputStream.markSupported()) { 44 this.inputStream = inputStream; 45 } else { 46 this.inputStream = new BufferedInputStream(inputStream); 47 } 48 } 49 50 @Override 51 public void close() { 52 try { 53 this.inputStream.close(); 54 } catch (IOException e) { 55 Logging.error(e); 56 } 57 } 58 59 /** 60 * Get the "next" WireType 61 * @return {@link WireType} expected 62 * @throws IOException - if an IO error occurs 63 */ 64 public WireType next() throws IOException { 65 this.inputStream.mark(16); 66 try { 67 return WireType.values()[this.inputStream.read() << 3]; 68 } finally { 69 this.inputStream.reset(); 70 } 71 } 72 73 /** 74 * Get the next byte 75 * @return The next byte 76 * @throws IOException - if an IO error occurs 77 */ 78 public int nextByte() throws IOException { 79 return this.inputStream.read(); 80 } 81 82 /** 83 * Check if there is more data to read 84 * @return {@code true} if there is more data to read 85 * @throws IOException - if an IO error occurs 86 */ 87 public boolean hasNext() throws IOException { 88 return this.inputStream.available() > 0; 89 } 90 91 /** 92 * Get the next var int ({@code WireType#VARINT}) 93 * @return The next var int ({@code int32}, {@code int64}, {@code uint32}, {@code uint64}, {@code bool}, {@code enum}) 94 * @throws IOException - if an IO error occurs 95 */ 96 public byte[] nextVarInt() throws IOException { 97 List<Byte> byteList = new ArrayList<>(); 98 int currentByte = this.nextByte(); 99 while ((byte) (currentByte & MOST_SIGNIFICANT_BYTE) == MOST_SIGNIFICANT_BYTE) { 100 // Get rid of the leading bit (shift left 1, then shift right 1 unsigned) 101 byteList.add((byte) (currentByte ^ MOST_SIGNIFICANT_BYTE)); 102 currentByte = this.nextByte(); 103 } 104 // The last byte doesn't drop the most significant bit 105 byteList.add((byte) currentByte); 106 byte[] byteArray = new byte[byteList.size()]; 107 for (int i = 0; i < byteList.size(); i++) { 108 byteArray[i] = byteList.get(i); 109 } 110 111 return byteArray; 112 } 113 114 /** 115 * Get the next 32 bits ({@link WireType#THIRTY_TWO_BIT}) 116 * @return a byte array of the next 32 bits (4 bytes) 117 * @throws IOException - if an IO error occurs 118 */ 119 public byte[] nextFixed32() throws IOException { 120 // 4 bytes == 32 bits 121 return readNextBytes(4); 122 } 123 124 /** 125 * Get the next 64 bits ({@link WireType#SIXTY_FOUR_BIT}) 126 * @return a byte array of the next 64 bits (8 bytes) 127 * @throws IOException - if an IO error occurs 128 */ 129 public byte[] nextFixed64() throws IOException { 130 // 8 bytes == 64 bits 131 return readNextBytes(8); 132 } 133 134 /** 135 * Read an arbitrary number of bytes 136 * @param size The number of bytes to read 137 * @return a byte array of the specified size, filled with bytes read (unsigned) 138 * @throws IOException - if an IO error occurs 139 */ 140 private byte[] readNextBytes(int size) throws IOException { 141 byte[] bytesRead = new byte[size]; 142 for (int i = 0; i < bytesRead.length; i++) { 143 bytesRead[i] = (byte) this.nextByte(); 144 } 145 return bytesRead; 146 } 147 148 /** 149 * Get the next delimited message ({@link WireType#LENGTH_DELIMITED}) 150 * @return The next length delimited message 151 * @throws IOException - if an IO error occurs 152 */ 153 public byte[] nextLengthDelimited() throws IOException { 154 int length = convertByteArray(this.nextVarInt(), VAR_INT_BYTE_SIZE).intValue(); 155 return readNextBytes(length); 156 } 157 158 /** 159 * Convert a byte array to a number (little endian) 160 * @param bytes The bytes to convert 161 * @param byteSize The size of the byte. For var ints, this is 7, for other ints, this is 8. 162 * @return An appropriate {@link Number} class. 163 */ 164 public static Number convertByteArray(byte[] bytes, byte byteSize) { 165 long number = 0; 166 for (int i = 0; i < bytes.length; i++) { 167 // Need to convert to uint64 in order to avoid bit operation from filling in 1's and overflow issues 168 number += Byte.toUnsignedLong(bytes[i]) << (byteSize * i); 169 } 170 return convertLong(number); 171 } 172 173 /** 174 * Convert a long to an appropriate {@link Number} class 175 * @param number The long to convert 176 * @return A {@link Number} 177 */ 178 public static Number convertLong(long number) { 179 // TODO deal with booleans 180 if (number <= Byte.MAX_VALUE && number >= Byte.MIN_VALUE) { 181 return (byte) number; 182 } else if (number <= Short.MAX_VALUE && number >= Short.MIN_VALUE) { 183 return (short) number; 184 } else if (number <= Integer.MAX_VALUE && number >= Integer.MIN_VALUE) { 185 return (int) number; 186 } 187 return number; 188 } 189 190 /** 191 * Read all records 192 * @return A collection of all records 193 * @throws IOException - if an IO error occurs 194 */ 195 public Collection<ProtoBufRecord> allRecords() throws IOException { 196 Collection<ProtoBufRecord> records = new ArrayList<>(); 197 while (this.hasNext()) { 198 records.add(new ProtoBufRecord(this)); 199 } 200 return records; 201 } 202 } -
src/org/openstreetmap/josm/data/protobuf/ProtoBufRecord.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.protobuf; 3 4 import java.io.IOException; 5 import java.nio.charset.StandardCharsets; 6 import java.util.stream.Stream; 7 8 import org.openstreetmap.josm.tools.Utils; 9 10 /** 11 * A protobuf record, storing the {@link WireType}, the parsed field number, and the bytes for it. 12 * @author Taylor Smock 13 * @since xxx 14 */ 15 public class ProtoBufRecord implements AutoCloseable { 16 private static final byte[] EMPTY_BYTES = {}; 17 private final WireType type; 18 private final int field; 19 private byte[] bytes; 20 21 /** 22 * Create a new Protobuf record 23 * @param parser The parser to use to create the record 24 * @throws IOException - if an IO error occurs 25 */ 26 public ProtoBufRecord(ProtoBufParser parser) throws IOException { 27 Number number = ProtoBufParser.convertByteArray(parser.nextVarInt(), ProtoBufParser.VAR_INT_BYTE_SIZE); 28 // I don't foresee having field numbers > {@code Integer#MAX_VALUE >> 3} 29 this.field = (int) number.longValue() >> 3; 30 // 7 is 111 (so last three bits) 31 byte wireType = (byte) (number.longValue() & 7); 32 this.type = Stream.of(WireType.values()).filter(wType -> wType.getTypeRepresentation() == wireType).findFirst().orElse(WireType.UNKNOWN); 33 34 if (this.type == WireType.VARINT) { 35 this.bytes = parser.nextVarInt(); 36 } else if (this.type == WireType.SIXTY_FOUR_BIT) { 37 this.bytes = parser.nextFixed64(); 38 } else if (this.type == WireType.THIRTY_TWO_BIT) { 39 this.bytes = parser.nextFixed32(); 40 } else if (this.type == WireType.LENGTH_DELIMITED) { 41 this.bytes = parser.nextLengthDelimited(); 42 } else { 43 this.bytes = EMPTY_BYTES; 44 } 45 } 46 47 /** 48 * Get the field value 49 * @return The field value 50 */ 51 public int getField() { 52 return this.field; 53 } 54 55 /** 56 * Get the WireType of the data 57 * @return The {@link WireType} of the data 58 */ 59 public WireType getType() { 60 return this.type; 61 } 62 63 /** 64 * Get the raw bytes for this record 65 * @return The bytes 66 */ 67 public byte[] getBytes() { 68 return this.bytes; 69 } 70 71 /** 72 * Get the var int ({@code WireType#VARINT}) 73 * @return The var int ({@code int32}, {@code int64}, {@code uint32}, {@code uint64}, {@code bool}, {@code enum}) 74 */ 75 public Number asUnsignedVarInt() { 76 return ProtoBufParser.convertByteArray(this.bytes, ProtoBufParser.VAR_INT_BYTE_SIZE); 77 } 78 79 /** 80 * Get the signed var int ({@code WireType#VARINT}). 81 * These are specially encoded so that they take up less space. 82 * 83 * @return The signed var int ({@code sint32} or {@code sint64}) 84 */ 85 public Number asSignedVarInt() { 86 final Number signed = this.asUnsignedVarInt(); 87 final long value = signed.longValue(); 88 final long number = (value << 1) ^ (value >> 31); 89 return ProtoBufParser.convertLong(number); 90 } 91 92 /** 93 * Get as a double ({@link WireType#SIXTY_FOUR_BIT}) 94 * @return the double 95 */ 96 public double asDouble() { 97 long doubleNumber = ProtoBufParser.convertByteArray(asFixed64(), ProtoBufParser.BYTE_SIZE).longValue(); 98 return Double.longBitsToDouble(doubleNumber); 99 } 100 101 /** 102 * Get as a float ({@link WireType#THIRTY_TWO_BIT}) 103 * @return the float 104 */ 105 public float asFloat() { 106 int floatNumber = ProtoBufParser.convertByteArray(asFixed32(), ProtoBufParser.BYTE_SIZE).intValue(); 107 return Float.intBitsToFloat(floatNumber); 108 } 109 110 /** 111 * Get as a string ({@link WireType#LENGTH_DELIMITED}) 112 * @return The string (encoded as {@link StandardCharsets#UTF_8}) 113 */ 114 public String asString() { 115 return Utils.intern(new String(this.bytes, StandardCharsets.UTF_8)); 116 } 117 118 /** 119 * Get as 32 bits ({@link WireType#THIRTY_TWO_BIT}) 120 * @return a byte array of the 32 bits (4 bytes) 121 */ 122 public byte[] asFixed32() { 123 // TODO verify, or just assume? 124 // 4 bytes == 32 bits 125 return this.bytes; 126 } 127 128 /** 129 * Get as 64 bits ({@link WireType#SIXTY_FOUR_BIT}) 130 * @return a byte array of the 64 bits (8 bytes) 131 */ 132 public byte[] asFixed64() { 133 // TODO verify, or just assume? 134 // 8 bytes == 64 bits 135 return this.bytes; 136 } 137 138 @Override 139 public void close() { 140 this.bytes = null; 141 } 142 } -
src/org/openstreetmap/josm/data/protobuf/WireType.java
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 } -
src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
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 } … … 1007 1008 } 1008 1009 } 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.currentZoomLevel, this); 1033 if (clip != null) { 1034 g.setClip(oldClip); 1035 } 1036 } 1037 1010 1038 private List<Tile> paintTileImages(Graphics2D g, TileSet ts) { 1011 1039 Object paintMutex = new Object(); 1012 1040 List<TilePosition> missed = Collections.synchronizedList(new ArrayList<>()); … … 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
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/layer/imagery/MVTLayer.java
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 /** 18 * A layer for MapBox Vector Tiles 19 * @author Taylor Smock 20 * @since xxx 21 */ 22 public class MVTLayer extends AbstractCachedTileSourceLayer<MapboxVectorTileSource> { 23 private static final String CACHE_REGION_NAME = "MVT"; 24 25 public MVTLayer(ImageryInfo info) { 26 super(info); 27 } 28 29 @Override 30 protected Class<? extends TileLoader> getTileLoaderClass() { 31 return MapBoxVectorCachedTileLoader.class; 32 } 33 34 @Override 35 protected String getCacheName() { 36 return CACHE_REGION_NAME; 37 } 38 39 @Override 40 public Collection<String> getNativeProjections() { 41 // MapBox Vector Tiles <i>specifically</i> only support EPSG:3857 42 // ("it is exclusively geared towards square pixel tiles in {link to EPSG:3857}"). 43 return Collections.singleton(MVTFile.DEFAULT_PROJECTION); 44 } 45 46 @Override 47 protected MapboxVectorTileSource getTileSource() { 48 MapboxVectorTileSource source = new MapboxVectorTileSource(this.info); 49 this.info.setAttribution(source); 50 return source; 51 } 52 53 @Override 54 public Tile createTile(MapboxVectorTileSource source, int x, int y, int zoom) { 55 return new MVTTile(source, x, y, zoom); 56 } 57 58 } -
src/org/openstreetmap/josm/gui/preferences/imagery/AddMVTLayerPanel.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.preferences.imagery; 3 import org.openstreetmap.josm.data.imagery.ImageryInfo; 4 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType; 5 import org.openstreetmap.josm.gui.widgets.JosmTextArea; 6 import org.openstreetmap.josm.gui.widgets.JosmTextField; 7 import org.openstreetmap.josm.tools.GBC; 8 import org.openstreetmap.josm.tools.Utils; 9 10 import javax.swing.JLabel; 11 import java.awt.event.KeyAdapter; 12 import java.awt.event.KeyEvent; 13 import java.util.Arrays; 14 15 import static org.openstreetmap.josm.tools.I18n.tr; 16 17 /** 18 * A panel for adding MapBox Vector Tile layers 19 * @author Taylor Smock 20 * @since xxx 21 */ 22 public class AddMVTLayerPanel extends AddImageryPanel { 23 private final JosmTextField mvtZoom = new JosmTextField(); 24 private final JosmTextArea mvtUrl = new JosmTextArea(3, 40).transferFocusOnTab(); 25 26 /** 27 * Constructs a new {@code AddMVTLayerPanel}. 28 */ 29 public AddMVTLayerPanel() { 30 31 add(new JLabel(tr("{0} Make sure OSM has the permission to use this service", "1.")), GBC.eol()); 32 add(new JLabel(tr("{0} Enter URL", "2.")), GBC.eol()); 33 add(new JLabel("<html>" + Utils.joinAsHtmlUnorderedList(Arrays.asList( 34 tr("{0} is replaced by tile zoom level, also supported:<br>" + 35 "offsets to the zoom level: {1} or {2}<br>" + 36 "reversed zoom level: {3}", "{zoom}", "{zoom+1}", "{zoom-1}", "{19-zoom}"), 37 tr("{0} is replaced by X-coordinate of the tile", "{x}"), 38 tr("{0} is replaced by Y-coordinate of the tile", "{y}"), 39 tr("{0} is replaced by a random selection from the given comma separated list, e.g. {1}", "{switch:...}", "{switch:a,b,c}") 40 )) + "</html>"), GBC.eol().fill()); 41 42 final KeyAdapter keyAdapter = new KeyAdapter() { 43 @Override 44 public void keyReleased(KeyEvent e) { 45 mvtUrl.setText(buildMvtUrl()); 46 } 47 }; 48 49 add(rawUrl, GBC.eop().fill()); 50 rawUrl.setLineWrap(true); 51 rawUrl.addKeyListener(keyAdapter); 52 53 add(new JLabel(tr("{0} Enter maximum zoom (optional)", "3.")), GBC.eol()); 54 mvtZoom.addKeyListener(keyAdapter); 55 add(mvtZoom, GBC.eop().fill()); 56 57 add(new JLabel(tr("{0} Edit generated {1} URL (optional)", "4.", "MVT")), GBC.eol()); 58 add(mvtUrl, GBC.eop().fill()); 59 mvtUrl.setLineWrap(true); 60 61 add(new JLabel(tr("{0} Enter name for this layer", "5.")), GBC.eol()); 62 add(name, GBC.eop().fill()); 63 64 registerValidableComponent(mvtUrl); 65 } 66 67 private String buildMvtUrl() { 68 StringBuilder a = new StringBuilder("mvt"); 69 String z = sanitize(mvtZoom.getText()); 70 if (!z.isEmpty()) { 71 a.append('[').append(z).append(']'); 72 } 73 a.append(':').append(sanitize(getImageryRawUrl(), ImageryType.MVT)); 74 return a.toString(); 75 } 76 77 @Override 78 public ImageryInfo getImageryInfo() { 79 ImageryInfo generated = new ImageryInfo(getImageryName(), getMvtUrl()); 80 generated.setImageryType(ImageryType.MVT); 81 return generated; 82 } 83 84 protected final String getMvtUrl() { 85 return sanitize(mvtUrl.getText()); 86 } 87 88 @Override 89 protected boolean isImageryValid() { 90 return !getImageryName().isEmpty() && !getMvtUrl().isEmpty(); 91 } 92 } -
src/org/openstreetmap/josm/gui/preferences/imagery/ImageryProvidersPanel.java
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)); … … 440 441 case WMTS: 441 442 icon = /* ICON(dialogs/) */ "add_wmts"; 442 443 break; 444 case MVT: 445 icon = /* ICON(dialogs/) */ "add_mvt"; 446 break; 443 447 default: 444 448 break; 445 449 } … … 460 464 case WMTS: 461 465 p = new AddWMTSLayerPanel(); 462 466 break; 467 case MVT: 468 p = new AddMVTLayerPanel(); 469 break; 463 470 default: 464 471 throw new IllegalStateException("Type " + type + " not supported"); 465 472 } -
test/unit/org/openstreetmap/josm/data/protobuf/ProtoBufTest.java
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 /** 42 * Test reading tile from Mapillary ( 14/3251/6258 ) 43 * @throws IOException if there is a problem reading the file 44 */ 45 @Test 46 void testRead_14_3251_6258() throws IOException { 47 File vectorTile = Paths.get(TestUtils.getTestDataRoot(), "pbf", "6258.mvt").toFile(); 48 InputStream inputStream = Compression.getUncompressedFileInputStream(vectorTile); 49 Collection<ProtoBufRecord> records = new ProtoBufParser(inputStream).allRecords(); 50 assertEquals(2, records.size()); 51 List<Layer> layers = new ArrayList<>(); 52 for (ProtoBufRecord record : records) { 53 if (record.getField() == Layer.LAYER_FIELD) { 54 layers.add(new Layer(record.getBytes())); 55 } else { 56 fail(MessageFormat.format("Invalid field {0}", record.getField())); 57 } 58 } 59 Layer mapillarySequences = layers.get(0); 60 Layer mapillaryPictures = layers.get(1); 61 assertEquals("mapillary-sequences", mapillarySequences.getName()); 62 assertEquals("mapillary-images", mapillaryPictures.getName()); 63 assertEquals(8192, mapillarySequences.getExtent()); 64 assertEquals(8192, mapillaryPictures.getExtent()); 65 66 assertEquals(1, mapillarySequences.getFeatures().stream().filter(feature -> feature.getId() == 241083111).count()); 67 Feature testSequence = mapillarySequences.getFeatures().stream().filter(feature -> feature.getId() == 241083111).findAny().orElse(null); 68 assertEquals("jgxkXqVFM4jepMG3vP5Q9A", testSequence.getTags().get("key")); 69 assertEquals("C15Ul6qVMfQFlzRcmQCLcA", testSequence.getTags().get("ikey")); 70 assertEquals("x0hTY8cakpy0m3ui1GaG1A", testSequence.getTags().get("userkey")); 71 assertEquals(Long.valueOf(1565196718638L), Long.valueOf(testSequence.getTags().get("captured_at"))); 72 assertEquals(0, Integer.parseInt(testSequence.getTags().get("pano"))); 73 } 74 }