source: josm/trunk/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Layer.java

Last change on this file was 19108, checked in by taylor.smock, 6 months ago

Cleanup some new PMD warnings from PMD 7.x (followup of r19101)

File size: 10.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.imagery.vectortile.mapbox;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.ByteArrayOutputStream;
7import java.io.IOException;
8import java.util.ArrayList;
9import java.util.Arrays;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.List;
14import java.util.Map;
15import java.util.Objects;
16import java.util.function.Function;
17import java.util.stream.Collectors;
18
19import org.openstreetmap.josm.data.protobuf.ProtobufParser;
20import org.openstreetmap.josm.data.protobuf.ProtobufRecord;
21import org.openstreetmap.josm.tools.Destroyable;
22
23/**
24 * A Mapbox Vector Tile Layer
25 * @author Taylor Smock
26 * @since 17862
27 */
28public final class Layer implements Destroyable {
29 private static final class ValueFields<T> {
30 static final ValueFields<String> STRING = new ValueFields<>(1, ProtobufRecord::asString);
31 static final ValueFields<Float> FLOAT = new ValueFields<>(2, ProtobufRecord::asFloat);
32 static final ValueFields<Double> DOUBLE = new ValueFields<>(3, ProtobufRecord::asDouble);
33 static final ValueFields<Number> INT64 = new ValueFields<>(4, ProtobufRecord::asUnsignedVarInt);
34 // This may have issues if there are actual uint_values (i.e., more than {@link Long#MAX_VALUE})
35 static final ValueFields<Number> UINT64 = new ValueFields<>(5, ProtobufRecord::asUnsignedVarInt);
36 static final ValueFields<Number> SINT64 = new ValueFields<>(6, ProtobufRecord::asSignedVarInt);
37 static final ValueFields<Boolean> BOOL = new ValueFields<>(7, r -> r.asUnsignedVarInt().longValue() != 0);
38
39 /**
40 * A collection of methods to map a record to a type
41 */
42 public static final Collection<ValueFields<?>> MAPPERS =
43 Collections.unmodifiableList(Arrays.asList(STRING, FLOAT, DOUBLE, INT64, UINT64, SINT64, BOOL));
44
45 private final byte field;
46 private final Function<ProtobufRecord, T> conversion;
47 private ValueFields(int field, Function<ProtobufRecord, T> conversion) {
48 this.field = (byte) field;
49 this.conversion = conversion;
50 }
51
52 /**
53 * Get the field identifier for the value
54 * @return The identifier
55 */
56 public byte getField() {
57 return this.field;
58 }
59
60 /**
61 * Convert a protobuf record to a value
62 * @param protobufRecord The record to convert
63 * @return the converted value
64 */
65 public T convertValue(ProtobufRecord protobufRecord) {
66 return this.conversion.apply(protobufRecord);
67 }
68 }
69
70 /** The field value for a layer (in {@link ProtobufRecord#getField}) */
71 public static final byte LAYER_FIELD = 3;
72 private static final byte VERSION_FIELD = 15;
73 private static final byte NAME_FIELD = 1;
74 private static final byte FEATURE_FIELD = 2;
75 private static final byte KEY_FIELD = 3;
76 private static final byte VALUE_FIELD = 4;
77 private static final byte EXTENT_FIELD = 5;
78 /** The default extent for a vector tile */
79 static final int DEFAULT_EXTENT = 4096;
80 private static final byte DEFAULT_VERSION = 1;
81 /** This is <i>technically</i> an integer, but there are currently only two major versions (1, 2). Required. */
82 private final byte version;
83 /** A unique name for the layer. This <i>must</i> be unique on a per-tile basis. Required. */
84 private final String name;
85
86 /** The extent of the tile, typically 4096. Required. */
87 private final int extent;
88
89 /** A list of unique keys. Order is important. Optional. */
90 private final List<String> keyList = new ArrayList<>();
91 /** A list of unique values. Order is important. Optional. */
92 private final List<Object> valueList = new ArrayList<>();
93 /** The actual features of this layer in this tile */
94 private final List<Feature> featureCollection;
95
96 /**
97 * Create a layer from a collection of records
98 * @param records The records to convert to a layer
99 * @throws IOException - if an IO error occurs
100 */
101 @SuppressWarnings("PMD.CloseResource") // The resources _are_ closed after use; it just isn't detect with PMD 7.2.x.
102 public Layer(Collection<ProtobufRecord> records) throws IOException {
103 // Do the unique required fields first
104 Map<Integer, List<ProtobufRecord>> sorted = new HashMap<>(records.size());
105 byte tVersion = DEFAULT_VERSION;
106 String tName = null;
107 int tExtent = DEFAULT_EXTENT;
108 final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(4);
109 for (ProtobufRecord protobufRecord : records) {
110 if (protobufRecord.getField() == VERSION_FIELD) {
111 tVersion = protobufRecord.asUnsignedVarInt().byteValue();
112 // Per spec, we cannot continue past this until we have checked the version number
113 if (tVersion != 1 && tVersion != 2) {
114 throw new IllegalArgumentException(tr("We do not understand version {0} of the vector tile specification", tVersion));
115 }
116 } else if (protobufRecord.getField() == NAME_FIELD) {
117 tName = protobufRecord.asString();
118 } else if (protobufRecord.getField() == EXTENT_FIELD) {
119 tExtent = protobufRecord.asUnsignedVarInt().intValue();
120 } else if (protobufRecord.getField() == KEY_FIELD) {
121 this.keyList.add(protobufRecord.asString());
122 } else if (protobufRecord.getField() == VALUE_FIELD) {
123 parseValueRecord(byteArrayOutputStream, protobufRecord);
124 } else {
125 sorted.computeIfAbsent(protobufRecord.getField(), i -> new ArrayList<>(records.size())).add(protobufRecord);
126 }
127 }
128 this.version = tVersion;
129 if (tName == null) {
130 throw new IllegalArgumentException(tr("Vector tile layers must have a layer name"));
131 }
132 this.name = tName;
133 this.extent = tExtent;
134
135 this.featureCollection = new ArrayList<>(sorted.getOrDefault((int) FEATURE_FIELD, Collections.emptyList()).size());
136 for (ProtobufRecord protobufRecord : sorted.getOrDefault((int) FEATURE_FIELD, Collections.emptyList())) {
137 this.featureCollection.add(new Feature(this, protobufRecord));
138 }
139 // Cleanup bytes (for memory)
140 for (ProtobufRecord protobufRecord : records) { // NOSONAR -- this shouldn't be combined since this is a cleanup loop.
141 protobufRecord.close();
142 }
143 }
144
145 private void parseValueRecord(ByteArrayOutputStream byteArrayOutputStream, ProtobufRecord protobufRecord)
146 throws IOException {
147 try (ProtobufParser parser = new ProtobufParser(protobufRecord.getBytes())) {
148 ProtobufRecord protobufRecord2 = new ProtobufRecord(byteArrayOutputStream, parser);
149 int field = protobufRecord2.getField();
150 int valueListSize = this.valueList.size();
151 for (Layer.ValueFields<?> mapper : ValueFields.MAPPERS) {
152 if (mapper.getField() == field) {
153 this.valueList.add(mapper.convertValue(protobufRecord2));
154 break;
155 }
156 }
157 if (valueListSize == this.valueList.size()) {
158 throw new IllegalArgumentException(tr("Unknown field in vector tile layer value ({0})", field));
159 }
160 }
161 }
162
163 /**
164 * Get all the records from a array of bytes
165 * @param bytes The byte information
166 * @return All the protobuf records
167 * @throws IOException If there was an error reading the bytes (unlikely)
168 */
169 private static Collection<ProtobufRecord> getAllRecords(byte[] bytes) throws IOException {
170 try (ProtobufParser parser = new ProtobufParser(bytes)) {
171 return parser.allRecords();
172 }
173 }
174
175 /**
176 * Create a new layer
177 * @param bytes The bytes that the layer comes from
178 * @throws IOException - if an IO error occurs
179 */
180 public Layer(byte[] bytes) throws IOException {
181 this(getAllRecords(bytes));
182 }
183
184 /**
185 * Get the extent of the tile
186 * @return The layer extent
187 */
188 public int getExtent() {
189 return this.extent;
190 }
191
192 /**
193 * Get the feature on this layer
194 * @return the features
195 */
196 public Collection<Feature> getFeatures() {
197 return Collections.unmodifiableCollection(this.featureCollection);
198 }
199
200 /**
201 * Get the geometry for this layer
202 * @return The geometry
203 */
204 public Collection<Geometry> getGeometry() {
205 return getFeatures().stream().map(Feature::getGeometryObject).collect(Collectors.toList());
206 }
207
208 /**
209 * Get a specified key
210 * @param index The index in the key list
211 * @return The actual key
212 */
213 public String getKey(int index) {
214 return this.keyList.get(index);
215 }
216
217 /**
218 * Get the name of the layer
219 * @return The layer name
220 */
221 public String getName() {
222 return this.name;
223 }
224
225 /**
226 * Get a specified value
227 * @param index The index in the value list
228 * @return The actual value. This can be a {@link String}, {@link Boolean}, {@link Integer}, or {@link Float} value.
229 */
230 public Object getValue(int index) {
231 return this.valueList.get(index);
232 }
233
234 /**
235 * Get the Mapbox Vector Tile version specification for this layer
236 * @return The version of the Mapbox Vector Tile specification
237 */
238 public byte getVersion() {
239 return this.version;
240 }
241
242 @Override
243 public void destroy() {
244 this.featureCollection.clear();
245 this.keyList.clear();
246 this.valueList.clear();
247 }
248
249 @Override
250 public boolean equals(Object other) {
251 if (other instanceof Layer) {
252 Layer o = (Layer) other;
253 return this.extent == o.extent
254 && this.version == o.version
255 && Objects.equals(this.name, o.name)
256 && Objects.equals(this.featureCollection, o.featureCollection)
257 && Objects.equals(this.keyList, o.keyList)
258 && Objects.equals(this.valueList, o.valueList);
259 }
260 return false;
261 }
262
263 @Override
264 public int hashCode() {
265 return Objects.hash(this.name, this.version, this.extent, this.featureCollection, this.keyList, this.valueList);
266 }
267}
Note: See TracBrowser for help on using the repository browser.