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 | *
|
---|
18 | * @author Taylor Smock
|
---|
19 | * @since xxx
|
---|
20 | */
|
---|
21 | public class Feature {
|
---|
22 | private static final byte ID_FIELD = 1;
|
---|
23 | private static final byte TAG_FIELD = 2;
|
---|
24 | private static final byte GEOMETRY_TYPE_FIELD = 3;
|
---|
25 | private static final byte GEOMETRY_FIELD = 4;
|
---|
26 | /**
|
---|
27 | * The geometry of the feature. Required.
|
---|
28 | */
|
---|
29 | private final List<CommandInteger> geometry = new ArrayList<>();
|
---|
30 |
|
---|
31 | /**
|
---|
32 | * The geometry type of the feature. Required.
|
---|
33 | */
|
---|
34 | private final GeometryTypes geometryType;
|
---|
35 | /**
|
---|
36 | * The id of the feature. Optional.
|
---|
37 | */
|
---|
38 | // Technically, uint64
|
---|
39 | private final long id;
|
---|
40 | /**
|
---|
41 | * The tags of the feature. Optional.
|
---|
42 | */
|
---|
43 | private TagMap tags;
|
---|
44 | private Geometry geometryObject;
|
---|
45 |
|
---|
46 | /**
|
---|
47 | * Create a new Feature
|
---|
48 | *
|
---|
49 | * @param layer The layer the feature is part of (required for tags)
|
---|
50 | * @param record The record to create the feature from
|
---|
51 | * @throws IOException - if an IO error occurs
|
---|
52 | */
|
---|
53 | public Feature(Layer layer, ProtobufRecord record) throws IOException {
|
---|
54 | long tId = 0;
|
---|
55 | GeometryTypes geometryTypeTemp = GeometryTypes.UNKNOWN;
|
---|
56 | String key = null;
|
---|
57 | try (ProtobufParser parser = new ProtobufParser(record.getBytes())) {
|
---|
58 | while (parser.hasNext()) {
|
---|
59 | try (ProtobufRecord next = new ProtobufRecord(parser)) {
|
---|
60 | if (next.getField() == TAG_FIELD) {
|
---|
61 | if (tags == null) {
|
---|
62 | tags = new TagMap();
|
---|
63 | }
|
---|
64 | // This is packed in v1 and v2
|
---|
65 | ProtobufPacked packed = new ProtobufPacked(next.getBytes());
|
---|
66 | for (Number number : packed.getArray()) {
|
---|
67 | key = parseTagValue(key, layer, number);
|
---|
68 | }
|
---|
69 | } else if (next.getField() == GEOMETRY_FIELD) {
|
---|
70 | // This is packed in v1 and v2
|
---|
71 | ProtobufPacked packed = new ProtobufPacked(next.getBytes());
|
---|
72 | CommandInteger currentCommand = null;
|
---|
73 | for (Number number : packed.getArray()) {
|
---|
74 | if (currentCommand != null && currentCommand.hasAllExpectedParameters()) {
|
---|
75 | currentCommand = null;
|
---|
76 | }
|
---|
77 | if (currentCommand == null) {
|
---|
78 | currentCommand = new CommandInteger(number.intValue());
|
---|
79 | this.geometry.add(currentCommand);
|
---|
80 | } else {
|
---|
81 | currentCommand.addParameter(ProtobufParser.decodeZigZag(number));
|
---|
82 | }
|
---|
83 | }
|
---|
84 | // TODO fallback to non-packed
|
---|
85 | } else if (next.getField() == GEOMETRY_TYPE_FIELD) {
|
---|
86 | geometryTypeTemp = GeometryTypes.values()[next.asUnsignedVarInt().intValue()];
|
---|
87 | } else if (next.getField() == ID_FIELD) {
|
---|
88 | tId = next.asUnsignedVarInt().longValue();
|
---|
89 | }
|
---|
90 | }
|
---|
91 | }
|
---|
92 | }
|
---|
93 | this.id = tId;
|
---|
94 | this.geometryType = geometryTypeTemp;
|
---|
95 | record.close();
|
---|
96 | }
|
---|
97 |
|
---|
98 | /**
|
---|
99 | * Parse a tag value
|
---|
100 | *
|
---|
101 | * @param key The current key (or {@code null}, if {@code null}, the returned value will be the new key)
|
---|
102 | * @param layer The layer with key/value information
|
---|
103 | * @param number The number to get the value from
|
---|
104 | * @return The new key (if {@code null}, then a value was parsed and added to tags)
|
---|
105 | */
|
---|
106 | private String parseTagValue(String key, Layer layer, Number number) {
|
---|
107 | if (key == null) {
|
---|
108 | key = layer.getKey(number.intValue());
|
---|
109 | } else {
|
---|
110 | Object value = layer.getValue(number.intValue());
|
---|
111 | if (value instanceof Double || value instanceof Float) {
|
---|
112 | // reset grouping if the instance is a singleton
|
---|
113 | final NumberFormat numberFormat = NumberFormat.getNumberInstance();
|
---|
114 | final boolean grouping = numberFormat.isGroupingUsed();
|
---|
115 | try {
|
---|
116 | numberFormat.setGroupingUsed(false);
|
---|
117 | this.tags.put(key, numberFormat.format(value));
|
---|
118 | } finally {
|
---|
119 | numberFormat.setGroupingUsed(grouping);
|
---|
120 | }
|
---|
121 | } else {
|
---|
122 | this.tags.put(key, Utils.intern(value.toString()));
|
---|
123 | }
|
---|
124 | key = null;
|
---|
125 | }
|
---|
126 | return key;
|
---|
127 | }
|
---|
128 |
|
---|
129 | /**
|
---|
130 | * Get the geometry instructions
|
---|
131 | *
|
---|
132 | * @return The geometry
|
---|
133 | */
|
---|
134 | public List<CommandInteger> getGeometry() {
|
---|
135 | return this.geometry;
|
---|
136 | }
|
---|
137 |
|
---|
138 | /**
|
---|
139 | * Get the geometry type
|
---|
140 | *
|
---|
141 | * @return The {@link GeometryTypes}
|
---|
142 | */
|
---|
143 | public GeometryTypes getGeometryType() {
|
---|
144 | return this.geometryType;
|
---|
145 | }
|
---|
146 |
|
---|
147 | /**
|
---|
148 | * Get the id of the object
|
---|
149 | *
|
---|
150 | * @return The unique id in the layer, or 0.
|
---|
151 | */
|
---|
152 | public long getId() {
|
---|
153 | return this.id;
|
---|
154 | }
|
---|
155 |
|
---|
156 | /**
|
---|
157 | * Get the tags
|
---|
158 | *
|
---|
159 | * @return A tag map
|
---|
160 | */
|
---|
161 | public TagMap getTags() {
|
---|
162 | return this.tags;
|
---|
163 | }
|
---|
164 |
|
---|
165 | /**
|
---|
166 | * Get the an object with shapes for the geometry
|
---|
167 | * @return An object with usable geometry information
|
---|
168 | */
|
---|
169 | public Geometry getGeometryObject() {
|
---|
170 | if (this.geometryObject == null) {
|
---|
171 | this.geometryObject = new Geometry(this.getGeometryType(), this.getGeometry());
|
---|
172 | }
|
---|
173 | return this.geometryObject;
|
---|
174 | }
|
---|
175 | }
|
---|