Changeset 18441 in josm for trunk/src


Ignore:
Timestamp:
2022-05-10T17:30:28+02:00 (2 years ago)
Author:
taylor.smock
Message:

Fix #22022: Allow for recursive geojson parsing

This allows us to parse more geojsons without losing data.

This patch also removes the requirement that a new relation have
type=<SOME TYPE>. This should allow some usecases to work now
(for example, people can now examine the validation output from
some tools). We still don't add relations that have no tags and
have no members.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/io/GeoJSONReader.java

    r18007 r18441  
    1212import java.nio.charset.StandardCharsets;
    1313import java.util.ArrayList;
    14 import java.util.Arrays;
     14import java.util.Collections;
    1515import java.util.List;
    1616import java.util.Map;
     
    7070    /** The record separator is 0x1E per RFC 7464 */
    7171    private static final byte RECORD_SEPARATOR_BYTE = 0x1E;
    72     private Projection projection = Projections.getProjectionByCode("EPSG:4326"); // WGS 84
     72    /**
     73     * WGS 84 is the specified CRS for geojson -- alternate coordinate systems are considered to be deprecated from
     74     * GJ2008.
     75     */
     76    private static final String CRS_GEOJSON = "EPSG:4326";
     77    private Projection projection = Projections.getProjectionByCode(CRS_GEOJSON); // WGS 84
    7378
    7479    GeoJSONReader() {
     
    9398                JsonValue.ValueType valueType = object.get(FEATURES).getValueType();
    9499                CheckParameterUtil.ensureThat(valueType == JsonValue.ValueType.ARRAY, "features must be ARRAY, but is " + valueType);
    95                 parseFeatureCollection(object.getJsonArray(FEATURES));
     100                parseFeatureCollection(object.getJsonArray(FEATURES), false);
    96101                break;
    97102            case "Feature":
     
    99104                break;
    100105            case "GeometryCollection":
    101                 parseGeometryCollection(null, object);
     106                parseGeometryCollection(null, object, false);
    102107                break;
    103108            default:
     
    107112
    108113    /**
    109      * Parse CRS as per https://geojson.org/geojson-spec.html#coordinate-reference-system-objects.
     114     * Parse CRS as per <a href="https://geojson.org/geojson-spec.html#coordinate-reference-system-objects">
     115     * https://geojson.org/geojson-spec.html#coordinate-reference-system-objects</a>.
    110116     * CRS are obsolete in RFC7946 but still allowed for interoperability with older applications.
    111117     * Only named CRS are supported.
     
    117123        if (crs != null) {
    118124            // Inspired by https://github.com/JOSM/geojson/commit/f13ceed4645244612a63581c96e20da802779c56
    119             JsonObject properties = crs.getJsonObject("properties");
     125            JsonObject properties = crs.getJsonObject(PROPERTIES);
    120126            if (properties != null) {
    121127                switch (crs.getString(TYPE)) {
     
    124130                        if ("urn:ogc:def:crs:OGC:1.3:CRS84".equals(crsName)) {
    125131                            // https://osgeo-org.atlassian.net/browse/GEOT-1710
    126                             crsName = "EPSG:4326";
     132                            crsName = CRS_GEOJSON;
    127133                        } else if (crsName.startsWith("urn:ogc:def:crs:EPSG:")) {
    128134                            crsName = crsName.replace("urn:ogc:def:crs:", "");
    129135                        }
    130136                        projection = Optional.ofNullable(Projections.getProjectionByCode(crsName))
    131                                 .orElse(Projections.getProjectionByCode("EPSG:4326")); // WGS84
     137                                .orElse(Projections.getProjectionByCode(CRS_GEOJSON)); // WGS84
    132138                        break;
    133139                    case LINK: // Not supported (security risk)
     
    139145    }
    140146
    141     private void parseFeatureCollection(final JsonArray features) {
    142         for (JsonValue feature : features) {
    143             if (feature instanceof JsonObject) {
    144                 parseFeature((JsonObject) feature);
    145             }
    146         }
    147     }
    148 
    149     private void parseFeature(final JsonObject feature) {
     147    private Optional<? extends OsmPrimitive> parseFeatureCollection(final JsonArray features, boolean createRelation) {
     148        List<OsmPrimitive> primitives = features.stream().filter(JsonObject.class::isInstance).map(JsonObject.class::cast)
     149                .map(this::parseFeature).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
     150        if (createRelation && primitives.size() > 1) {
     151            Relation relation = new Relation();
     152            relation.setMembers(primitives.stream().map(osm -> new RelationMember("", osm)).collect(Collectors.toList()));
     153            getDataSet().addPrimitive(relation);
     154            return Optional.of(relation);
     155        } else if (primitives.size() == 1) {
     156            return Optional.of(primitives.get(0));
     157        }
     158        return Optional.empty();
     159    }
     160
     161    private Optional<? extends OsmPrimitive> parseFeature(final JsonObject feature) {
    150162        JsonValue geometry = feature.get(GEOMETRY);
    151163        if (geometry != null && geometry.getValueType() == JsonValue.ValueType.OBJECT) {
    152             parseGeometry(feature, geometry.asJsonObject());
     164            return parseGeometry(feature, geometry.asJsonObject());
    153165        } else {
    154166            JsonValue properties = feature.get(PROPERTIES);
    155167            if (properties != null && properties.getValueType() == JsonValue.ValueType.OBJECT) {
    156                 parseNonGeometryFeature(feature, properties.asJsonObject());
     168                return parseNonGeometryFeature(feature, properties.asJsonObject());
    157169            } else {
    158170                Logging.warn(tr("Relation/non-geometry feature without properties found: {0}", feature));
    159171            }
    160172        }
    161     }
    162 
    163     private void parseNonGeometryFeature(final JsonObject feature, final JsonObject properties) {
     173        return Optional.empty();
     174    }
     175
     176    private Optional<? extends OsmPrimitive> parseNonGeometryFeature(final JsonObject feature, final JsonObject properties) {
    164177        // get relation type
    165178        JsonValue type = properties.get(TYPE);
    166179        if (type == null || properties.getValueType() == JsonValue.ValueType.STRING) {
    167180            Logging.warn(tr("Relation/non-geometry feature without type found: {0}", feature));
    168             return;
     181            if (!feature.containsKey(FEATURES)) {
     182                return Optional.empty();
     183            }
    169184        }
    170185
    171186        // create misc. non-geometry feature
    172         final Relation relation = new Relation();
    173         fillTagsFromFeature(feature, relation);
    174         relation.put(TYPE, type.toString());
    175         getDataSet().addPrimitive(relation);
    176     }
    177 
    178     private void parseGeometryCollection(final JsonObject feature, final JsonObject geometry) {
     187        OsmPrimitive primitive = null;
     188        if (feature.containsKey(FEATURES) && feature.get(FEATURES).getValueType() == JsonValue.ValueType.ARRAY) {
     189            Optional<? extends OsmPrimitive> osm = parseFeatureCollection(feature.getJsonArray(FEATURES), true);
     190            if (osm.isPresent()) {
     191                primitive = osm.get();
     192                fillTagsFromFeature(feature, primitive);
     193            }
     194        }
     195        return Optional.ofNullable(primitive);
     196    }
     197
     198    private Optional<Relation> parseGeometryCollection(final JsonObject feature, final JsonObject geometry, boolean createRelation) {
     199        List<RelationMember> relationMembers = new ArrayList<>(geometry.getJsonArray("geometries").size());
    179200        for (JsonValue jsonValue : geometry.getJsonArray("geometries")) {
    180             parseGeometry(feature, jsonValue.asJsonObject());
    181         }
    182     }
    183 
    184     private void parseGeometry(final JsonObject feature, final JsonObject geometry) {
     201            parseGeometry(feature, jsonValue.asJsonObject()).map(osm -> new RelationMember("", osm)).ifPresent(relationMembers::add);
     202        }
     203        if (createRelation) {
     204            Relation relation = new Relation();
     205            relation.setMembers(relationMembers);
     206            getDataSet().addPrimitive(relation);
     207            return Optional.of(fillTagsFromFeature(feature, relation));
     208        }
     209        return Optional.empty();
     210    }
     211
     212    private Optional<? extends OsmPrimitive> parseGeometry(final JsonObject feature, final JsonObject geometry) {
    185213        if (geometry == null) {
    186214            parseNullGeometry(feature);
    187             return;
     215            return Optional.empty();
    188216        }
    189217
    190218        switch (geometry.getString(TYPE)) {
    191219            case "Point":
    192                 parsePoint(feature, geometry.getJsonArray(COORDINATES));
    193                 break;
     220                return parsePoint(feature, geometry.getJsonArray(COORDINATES));
    194221            case "MultiPoint":
    195                 parseMultiPoint(feature, geometry);
    196                 break;
     222                return parseMultiPoint(feature, geometry);
    197223            case "LineString":
    198                 parseLineString(feature, geometry.getJsonArray(COORDINATES));
    199                 break;
     224                return parseLineString(feature, geometry.getJsonArray(COORDINATES));
    200225            case "MultiLineString":
    201                 parseMultiLineString(feature, geometry);
    202                 break;
     226                return parseMultiLineString(feature, geometry);
    203227            case "Polygon":
    204                 parsePolygon(feature, geometry.getJsonArray(COORDINATES));
    205                 break;
     228                return parsePolygon(feature, geometry.getJsonArray(COORDINATES));
    206229            case "MultiPolygon":
    207                 parseMultiPolygon(feature, geometry);
    208                 break;
     230                return parseMultiPolygon(feature, geometry);
    209231            case "GeometryCollection":
    210                 parseGeometryCollection(feature, geometry);
    211                 break;
     232                return parseGeometryCollection(feature, geometry, true);
    212233            default:
    213234                parseUnknown(geometry);
     235                return Optional.empty();
    214236        }
    215237    }
     
    231253    }
    232254
    233     private void parsePoint(final JsonObject feature, final JsonArray coordinates) {
    234         fillTagsFromFeature(feature, createNode(getLatLon(coordinates)));
    235     }
    236 
    237     private void parseMultiPoint(final JsonObject feature, final JsonObject geometry) {
     255    private Optional<Node> parsePoint(final JsonObject feature, final JsonArray coordinates) {
     256        return Optional.of(fillTagsFromFeature(feature, createNode(getLatLon(coordinates))));
     257    }
     258
     259    private Optional<Relation> parseMultiPoint(final JsonObject feature, final JsonObject geometry) {
     260        List<RelationMember> nodes = new ArrayList<>(geometry.getJsonArray(COORDINATES).size());
    238261        for (JsonValue coordinate : geometry.getJsonArray(COORDINATES)) {
    239             parsePoint(feature, coordinate.asJsonArray());
    240         }
    241     }
    242 
    243     private void parseLineString(final JsonObject feature, final JsonArray coordinates) {
     262            parsePoint(feature, coordinate.asJsonArray()).map(node -> new RelationMember("", node)).ifPresent(nodes::add);
     263        }
     264        Relation returnRelation = new Relation();
     265        returnRelation.setMembers(nodes);
     266        getDataSet().addPrimitive(returnRelation);
     267        return Optional.of(fillTagsFromFeature(feature, returnRelation));
     268    }
     269
     270    private Optional<Way> parseLineString(final JsonObject feature, final JsonArray coordinates) {
    244271        if (!coordinates.isEmpty()) {
    245             createWay(coordinates, false)
    246                 .ifPresent(way -> fillTagsFromFeature(feature, way));
    247         }
    248     }
    249 
    250     private void parseMultiLineString(final JsonObject feature, final JsonObject geometry) {
     272            Optional<Way> way = createWay(coordinates, false);
     273            way.ifPresent(tWay -> fillTagsFromFeature(feature, tWay));
     274            return way;
     275        }
     276        return Optional.empty();
     277    }
     278
     279    private Optional<Relation> parseMultiLineString(final JsonObject feature, final JsonObject geometry) {
     280        final List<RelationMember> ways = new ArrayList<>(geometry.getJsonArray(COORDINATES).size());
    251281        for (JsonValue coordinate : geometry.getJsonArray(COORDINATES)) {
    252             parseLineString(feature, coordinate.asJsonArray());
    253         }
    254     }
    255 
    256     private void parsePolygon(final JsonObject feature, final JsonArray coordinates) {
     282            parseLineString(feature, coordinate.asJsonArray()).map(way -> new RelationMember("", way)).ifPresent(ways::add);
     283        }
     284        final Relation relation = new Relation();
     285        relation.setMembers(ways);
     286        getDataSet().addPrimitive(relation);
     287        return Optional.of(fillTagsFromFeature(feature, relation));
     288    }
     289
     290    private Optional<? extends OsmPrimitive> parsePolygon(final JsonObject feature, final JsonArray coordinates) {
    257291        final int size = coordinates.size();
    258292        if (size == 1) {
    259             createWay(coordinates.getJsonArray(0), true)
    260                 .ifPresent(way -> fillTagsFromFeature(feature, way));
     293            Optional<Way> optionalWay = createWay(coordinates.getJsonArray(0), true);
     294            optionalWay.ifPresent(way -> fillTagsFromFeature(feature, way));
     295            return optionalWay;
    261296        } else if (size > 1) {
    262297            // create multipolygon
     
    273308            multipolygon.put(TYPE, "multipolygon");
    274309            getDataSet().addPrimitive(multipolygon);
    275         }
    276     }
    277 
    278     private void parseMultiPolygon(final JsonObject feature, final JsonObject geometry) {
     310            return Optional.of(multipolygon);
     311        }
     312        return Optional.empty();
     313    }
     314
     315    private Optional<Relation> parseMultiPolygon(final JsonObject feature, final JsonObject geometry) {
     316        List<RelationMember> relationMembers = new ArrayList<>(geometry.getJsonArray(COORDINATES).size());
    279317        for (JsonValue coordinate : geometry.getJsonArray(COORDINATES)) {
    280             parsePolygon(feature, coordinate.asJsonArray());
    281         }
     318            parsePolygon(feature, coordinate.asJsonArray()).map(poly -> new RelationMember("", poly)).ifPresent(relationMembers::add);
     319        }
     320        Relation relation = new Relation();
     321        relation.setMembers(relationMembers);
     322        return Optional.of(fillTagsFromFeature(feature, relation));
    282323    }
    283324
     
    337378     * @param feature the GeoJSON feature
    338379     * @param primitive the OSM primitive
     380     * @param <O> The primitive type
     381     * @return The primitive passed in as {@code primitive} for easier chaining
    339382     */
    340     private static void fillTagsFromFeature(final JsonObject feature, final OsmPrimitive primitive) {
     383    private static <O extends OsmPrimitive> O fillTagsFromFeature(final JsonObject feature, final O primitive) {
    341384        if (feature != null) {
    342385            TagCollection featureTags = getTags(feature);
    343386            primitive.setKeys(new TagMap(primitive.isTagged() ? mergeAllTagValues(primitive, featureTags) : featureTags));
    344387        }
     388        return primitive;
    345389    }
    346390
     
    348392        TagCollection tags = TagCollection.from(primitive).union(featureTags);
    349393        TagConflictResolutionUtil.applyAutomaticTagConflictResolution(tags);
    350         TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing(tags, Arrays.asList(primitive));
     394        TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing(tags, Collections.singletonList(primitive));
    351395        TagConflictResolverModel tagModel = new TagConflictResolverModel();
    352396        tagModel.populate(new TagCollection(tags), tags.getKeysWithMultipleValues());
     
    455499                Way replacement = null;
    456500                for (OsmPrimitive p : e.getPrimitives()) {
    457                     if (p.isTagged() && p.referrers(Relation.class).count() == 0)
     501                    if (p.isTagged() && !p.referrers(Relation.class).findAny().isPresent())
    458502                        replacement = (Way) p;
    459503                    else if (p.referrers(Relation.class).anyMatch(Relation::isMultipolygon))
Note: See TracChangeset for help on using the changeset viewer.