001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.imagery.vectortile.mapbox; 003import static org.openstreetmap.josm.tools.I18n.tr; 004 005import java.io.IOException; 006import java.util.ArrayList; 007import java.util.Arrays; 008import java.util.Collection; 009import java.util.Collections; 010import java.util.HashSet; 011import java.util.List; 012import java.util.Map; 013import java.util.Objects; 014import java.util.function.Function; 015import java.util.stream.Collectors; 016 017import org.openstreetmap.josm.data.protobuf.ProtobufParser; 018import org.openstreetmap.josm.data.protobuf.ProtobufRecord; 019import org.openstreetmap.josm.tools.Destroyable; 020import org.openstreetmap.josm.tools.Logging; 021 022/** 023 * A Mapbox Vector Tile Layer 024 * @author Taylor Smock 025 * @since 17862 026 */ 027public final class Layer implements Destroyable { 028 private static final class ValueFields<T> { 029 static final ValueFields<String> STRING = new ValueFields<>(1, ProtobufRecord::asString); 030 static final ValueFields<Float> FLOAT = new ValueFields<>(2, ProtobufRecord::asFloat); 031 static final ValueFields<Double> DOUBLE = new ValueFields<>(3, ProtobufRecord::asDouble); 032 static final ValueFields<Number> INT64 = new ValueFields<>(4, ProtobufRecord::asUnsignedVarInt); 033 // This may have issues if there are actual uint_values (i.e., more than {@link Long#MAX_VALUE}) 034 static final ValueFields<Number> UINT64 = new ValueFields<>(5, ProtobufRecord::asUnsignedVarInt); 035 static final ValueFields<Number> SINT64 = new ValueFields<>(6, ProtobufRecord::asSignedVarInt); 036 static final ValueFields<Boolean> BOOL = new ValueFields<>(7, r -> r.asUnsignedVarInt().longValue() != 0); 037 038 /** 039 * A collection of methods to map a record to a type 040 */ 041 public static final Collection<ValueFields<?>> MAPPERS = 042 Collections.unmodifiableList(Arrays.asList(STRING, FLOAT, DOUBLE, INT64, UINT64, SINT64, BOOL)); 043 044 private final byte field; 045 private final Function<ProtobufRecord, T> conversion; 046 private ValueFields(int field, Function<ProtobufRecord, T> conversion) { 047 this.field = (byte) field; 048 this.conversion = conversion; 049 } 050 051 /** 052 * Get the field identifier for the value 053 * @return The identifier 054 */ 055 public byte getField() { 056 return this.field; 057 } 058 059 /** 060 * Convert a protobuf record to a value 061 * @param protobufRecord The record to convert 062 * @return the converted value 063 */ 064 public T convertValue(ProtobufRecord protobufRecord) { 065 return this.conversion.apply(protobufRecord); 066 } 067 } 068 069 /** The field value for a layer (in {@link ProtobufRecord#getField}) */ 070 public static final byte LAYER_FIELD = 3; 071 private static final byte VERSION_FIELD = 15; 072 private static final byte NAME_FIELD = 1; 073 private static final byte FEATURE_FIELD = 2; 074 private static final byte KEY_FIELD = 3; 075 private static final byte VALUE_FIELD = 4; 076 private static final byte EXTENT_FIELD = 5; 077 /** The default extent for a vector tile */ 078 static final int DEFAULT_EXTENT = 4096; 079 private static final byte DEFAULT_VERSION = 1; 080 /** This is <i>technically</i> an integer, but there are currently only two major versions (1, 2). Required. */ 081 private final byte version; 082 /** A unique name for the layer. This <i>must</i> be unique on a per-tile basis. Required. */ 083 private final String name; 084 085 /** The extent of the tile, typically 4096. Required. */ 086 private final int extent; 087 088 /** A list of unique keys. Order is important. Optional. */ 089 private final List<String> keyList = new ArrayList<>(); 090 /** A list of unique values. Order is important. Optional. */ 091 private final List<Object> valueList = new ArrayList<>(); 092 /** The actual features of this layer in this tile */ 093 private final List<Feature> featureCollection; 094 095 /** 096 * Create a layer from a collection of records 097 * @param records The records to convert to a layer 098 * @throws IOException - if an IO error occurs 099 */ 100 public Layer(Collection<ProtobufRecord> records) throws IOException { 101 // Do the unique required fields first 102 Map<Integer, List<ProtobufRecord>> sorted = records.stream().collect(Collectors.groupingBy(ProtobufRecord::getField)); 103 this.version = sorted.getOrDefault((int) VERSION_FIELD, Collections.emptyList()).parallelStream() 104 .map(ProtobufRecord::asUnsignedVarInt).map(Number::byteValue).findFirst().orElse(DEFAULT_VERSION); 105 // Per spec, we cannot continue past this until we have checked the version number 106 if (this.version != 1 && this.version != 2) { 107 throw new IllegalArgumentException(tr("We do not understand version {0} of the vector tile specification", this.version)); 108 } 109 this.name = sorted.getOrDefault((int) NAME_FIELD, Collections.emptyList()).parallelStream().map(ProtobufRecord::asString).findFirst() 110 .orElseThrow(() -> new IllegalArgumentException(tr("Vector tile layers must have a layer name"))); 111 this.extent = sorted.getOrDefault((int) EXTENT_FIELD, Collections.emptyList()).parallelStream().map(ProtobufRecord::asUnsignedVarInt) 112 .map(Number::intValue).findAny().orElse(DEFAULT_EXTENT); 113 114 sorted.getOrDefault((int) KEY_FIELD, Collections.emptyList()).parallelStream().map(ProtobufRecord::asString) 115 .forEachOrdered(this.keyList::add); 116 sorted.getOrDefault((int) VALUE_FIELD, Collections.emptyList()).parallelStream().map(ProtobufRecord::getBytes) 117 .map(ProtobufParser::new).map(parser1 -> { 118 try { 119 return new ProtobufRecord(parser1); 120 } catch (IOException e) { 121 Logging.error(e); 122 return null; 123 } 124 }) 125 .filter(Objects::nonNull) 126 .map(value -> ValueFields.MAPPERS.parallelStream() 127 .filter(v -> v.getField() == value.getField()) 128 .map(v -> v.convertValue(value)).findFirst() 129 .orElseThrow(() -> new IllegalArgumentException(tr("Unknown field in vector tile layer value ({0})", value.getField())))) 130 .forEachOrdered(this.valueList::add); 131 Collection<IOException> exceptions = new HashSet<>(0); 132 this.featureCollection = sorted.getOrDefault((int) FEATURE_FIELD, Collections.emptyList()).parallelStream().map(feature -> { 133 try { 134 return new Feature(this, feature); 135 } catch (IOException e) { 136 exceptions.add(e); 137 } 138 return null; 139 }).collect(Collectors.toList()); 140 if (!exceptions.isEmpty()) { 141 throw exceptions.iterator().next(); 142 } 143 // Cleanup bytes (for memory) 144 for (ProtobufRecord record : records) { 145 record.close(); 146 } 147 } 148 149 /** 150 * Get all the records from a array of bytes 151 * @param bytes The byte information 152 * @return All the protobuf records 153 * @throws IOException If there was an error reading the bytes (unlikely) 154 */ 155 private static Collection<ProtobufRecord> getAllRecords(byte[] bytes) throws IOException { 156 try (ProtobufParser parser = new ProtobufParser(bytes)) { 157 return parser.allRecords(); 158 } 159 } 160 161 /** 162 * Create a new layer 163 * @param bytes The bytes that the layer comes from 164 * @throws IOException - if an IO error occurs 165 */ 166 public Layer(byte[] bytes) throws IOException { 167 this(getAllRecords(bytes)); 168 } 169 170 /** 171 * Get the extent of the tile 172 * @return The layer extent 173 */ 174 public int getExtent() { 175 return this.extent; 176 } 177 178 /** 179 * Get the feature on this layer 180 * @return the features 181 */ 182 public Collection<Feature> getFeatures() { 183 return Collections.unmodifiableCollection(this.featureCollection); 184 } 185 186 /** 187 * Get the geometry for this layer 188 * @return The geometry 189 */ 190 public Collection<Geometry> getGeometry() { 191 return getFeatures().stream().map(Feature::getGeometryObject).collect(Collectors.toList()); 192 } 193 194 /** 195 * Get a specified key 196 * @param index The index in the key list 197 * @return The actual key 198 */ 199 public String getKey(int index) { 200 return this.keyList.get(index); 201 } 202 203 /** 204 * Get the name of the layer 205 * @return The layer name 206 */ 207 public String getName() { 208 return this.name; 209 } 210 211 /** 212 * Get a specified value 213 * @param index The index in the value list 214 * @return The actual value. This can be a {@link String}, {@link Boolean}, {@link Integer}, or {@link Float} value. 215 */ 216 public Object getValue(int index) { 217 return this.valueList.get(index); 218 } 219 220 /** 221 * Get the Mapbox Vector Tile version specification for this layer 222 * @return The version of the Mapbox Vector Tile specification 223 */ 224 public byte getVersion() { 225 return this.version; 226 } 227 228 @Override 229 public void destroy() { 230 this.featureCollection.clear(); 231 this.keyList.clear(); 232 this.valueList.clear(); 233 } 234 235 @Override 236 public boolean equals(Object other) { 237 if (other instanceof Layer) { 238 Layer o = (Layer) other; 239 return this.extent == o.extent 240 && this.version == o.version 241 && Objects.equals(this.name, o.name) 242 && Objects.equals(this.featureCollection, o.featureCollection) 243 && Objects.equals(this.keyList, o.keyList) 244 && Objects.equals(this.valueList, o.valueList); 245 } 246 return false; 247 } 248 249 @Override 250 public int hashCode() { 251 return Objects.hash(this.name, this.version, this.extent, this.featureCollection, this.keyList, this.valueList); 252 } 253}