Changeset 14086 in josm for trunk/src


Ignore:
Timestamp:
2018-08-05T14:09:31+02:00 (6 years ago)
Author:
Don-vip
Message:

fix #16546 - Support Overpass API JSON format

Location:
trunk/src/org/openstreetmap/josm/io
Files:
1 added
3 edited

Legend:

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

    r13197 r14086  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.io.IOException;
    67import java.io.InputStream;
     8import java.io.InputStreamReader;
     9import java.text.MessageFormat;
    710import java.util.ArrayList;
    811import java.util.Collection;
     
    1114import java.util.Map;
    1215import java.util.Map.Entry;
    13 
     16import java.util.function.Consumer;
     17
     18import org.openstreetmap.josm.data.Bounds;
     19import org.openstreetmap.josm.data.DataSource;
     20import org.openstreetmap.josm.data.coor.LatLon;
     21import org.openstreetmap.josm.data.osm.AbstractPrimitive;
    1422import org.openstreetmap.josm.data.osm.Changeset;
    1523import org.openstreetmap.josm.data.osm.DataSet;
     24import org.openstreetmap.josm.data.osm.DownloadPolicy;
    1625import org.openstreetmap.josm.data.osm.Node;
     26import org.openstreetmap.josm.data.osm.NodeData;
    1727import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1828import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     29import org.openstreetmap.josm.data.osm.PrimitiveData;
    1930import org.openstreetmap.josm.data.osm.PrimitiveId;
    2031import org.openstreetmap.josm.data.osm.Relation;
     32import org.openstreetmap.josm.data.osm.RelationData;
    2133import org.openstreetmap.josm.data.osm.RelationMember;
    2234import org.openstreetmap.josm.data.osm.RelationMemberData;
    2335import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
     36import org.openstreetmap.josm.data.osm.Tagged;
     37import org.openstreetmap.josm.data.osm.UploadPolicy;
     38import org.openstreetmap.josm.data.osm.User;
    2439import org.openstreetmap.josm.data.osm.Way;
     40import org.openstreetmap.josm.data.osm.WayData;
     41import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    2542import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     43import org.openstreetmap.josm.tools.CheckParameterUtil;
    2644import org.openstreetmap.josm.tools.Logging;
     45import org.openstreetmap.josm.tools.Utils;
     46import org.openstreetmap.josm.tools.date.DateUtils;
    2747
    2848/**
    2949 * Abstract Reader, allowing other implementations than OsmReader (PbfReader in PBF plugin for example)
    3050 * @author Vincent
    31  *
     51 * @since 4490
    3252 */
    3353public abstract class AbstractReader {
     54
     55    /** Used by plugins to register themselves as data postprocessors. */
     56    private static volatile List<OsmServerReadPostprocessor> postprocessors;
     57
     58    protected boolean cancel;
     59
     60    /**
     61     * Register a new postprocessor.
     62     * @param pp postprocessor
     63     * @see #deregisterPostprocessor
     64     * @since xxx (moved from OsmReader)
     65     */
     66    public static void registerPostprocessor(OsmServerReadPostprocessor pp) {
     67        if (postprocessors == null) {
     68            postprocessors = new ArrayList<>();
     69        }
     70        postprocessors.add(pp);
     71    }
     72
     73    /**
     74     * Deregister a postprocessor previously registered with {@link #registerPostprocessor}.
     75     * @param pp postprocessor
     76     * @see #registerPostprocessor
     77     * @since xxx (moved from OsmReader)
     78     */
     79    public static void deregisterPostprocessor(OsmServerReadPostprocessor pp) {
     80        if (postprocessors != null) {
     81            postprocessors.remove(pp);
     82        }
     83    }
    3484
    3585    /**
     
    63113    public DataSet getDataSet() {
    64114        return ds;
     115    }
     116
     117    /**
     118     * Iterate over registered postprocessors and give them each a chance to modify the dataset we have just loaded.
     119     * @param progressMonitor Progress monitor
     120     */
     121    protected void callPostProcessors(ProgressMonitor progressMonitor) {
     122        if (postprocessors != null) {
     123            for (OsmServerReadPostprocessor pp : postprocessors) {
     124                pp.postprocessDataSet(getDataSet(), progressMonitor);
     125            }
     126        }
    65127    }
    66128
     
    207269
    208270    protected abstract DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException;
     271
     272    @FunctionalInterface
     273    protected interface ParserWorker {
     274       void accept(InputStreamReader ir) throws IllegalDataException, IOException;
     275    }
     276
     277    protected final DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonitor, ParserWorker parserWorker)
     278            throws IllegalDataException {
     279        if (progressMonitor == null) {
     280            progressMonitor = NullProgressMonitor.INSTANCE;
     281        }
     282        ProgressMonitor.CancelListener cancelListener = () -> cancel = true;
     283        progressMonitor.addCancelListener(cancelListener);
     284        CheckParameterUtil.ensureParameterNotNull(source, "source");
     285        try {
     286            progressMonitor.beginTask(tr("Prepare OSM data...", 2));
     287            progressMonitor.indeterminateSubTask(tr("Parsing OSM data..."));
     288
     289            try (InputStreamReader ir = UTFInputStreamReader.create(source)) {
     290                parserWorker.accept(ir);
     291            }
     292            progressMonitor.worked(1);
     293
     294            boolean readOnly = getDataSet().isLocked();
     295
     296            progressMonitor.indeterminateSubTask(tr("Preparing data set..."));
     297            if (readOnly) {
     298                getDataSet().unlock();
     299            }
     300            prepareDataSet();
     301            if (readOnly) {
     302                getDataSet().lock();
     303            }
     304            progressMonitor.worked(1);
     305
     306            // iterate over registered postprocessors and give them each a chance
     307            // to modify the dataset we have just loaded.
     308            callPostProcessors(progressMonitor);
     309            // Make sure postprocessors did not change the read-only state
     310            if (readOnly && !getDataSet().isLocked()) {
     311                getDataSet().lock();
     312            }
     313            return getDataSet();
     314        } catch (IllegalDataException e) {
     315            throw e;
     316        } catch (IOException e) {
     317            throw new IllegalDataException(e);
     318        } finally {
     319            progressMonitor.finishTask();
     320            progressMonitor.removeCancelListener(cancelListener);
     321        }
     322    }
     323
     324    protected final long getLong(String name, String value) throws IllegalDataException {
     325        if (value == null) {
     326            throw new IllegalDataException(tr("Missing required attribute ''{0}''.", name));
     327        }
     328        try {
     329            return Long.parseLong(value);
     330        } catch (NumberFormatException e) {
     331            throw new IllegalDataException(tr("Illegal long value for attribute ''{0}''. Got ''{1}''.", name, value), e);
     332        }
     333    }
     334
     335    protected final void parseVersion(String version) throws IllegalDataException {
     336        validateVersion(version);
     337        ds.setVersion(version);
     338    }
     339
     340    private static void validateVersion(String version) throws IllegalDataException {
     341        if (version == null) {
     342            throw new IllegalDataException(tr("Missing mandatory attribute ''{0}''.", "version"));
     343        }
     344        if (!"0.6".equals(version)) {
     345            throw new IllegalDataException(tr("Unsupported version: {0}", version));
     346        }
     347    }
     348
     349    protected final void parseDownloadPolicy(String key, String downloadPolicy) throws IllegalDataException {
     350        parsePolicy(key, downloadPolicy, policy -> ds.setDownloadPolicy(DownloadPolicy.of(policy)));
     351    }
     352
     353    protected final void parseUploadPolicy(String key, String uploadPolicy) throws IllegalDataException {
     354        parsePolicy(key, uploadPolicy, policy -> ds.setUploadPolicy(UploadPolicy.of(policy)));
     355    }
     356
     357    private static void parsePolicy(String key, String policy, Consumer<String> consumer) throws IllegalDataException {
     358        if (policy != null) {
     359            try {
     360                consumer.accept(policy);
     361            } catch (IllegalArgumentException e) {
     362                throw new IllegalDataException(MessageFormat.format(
     363                        "Illegal value for attribute ''{0}''. Got ''{1}''.", key, policy), e);
     364            }
     365        }
     366    }
     367
     368    protected final void parseLocked(String locked) {
     369        if ("true".equalsIgnoreCase(locked)) {
     370            ds.lock();
     371        }
     372    }
     373
     374    protected final void parseBounds(String generator, String minlon, String minlat, String maxlon, String maxlat, String origin)
     375            throws IllegalDataException {
     376        if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
     377            if (origin == null) {
     378                origin = generator;
     379            }
     380            Bounds bounds = new Bounds(
     381                    Double.parseDouble(minlat), Double.parseDouble(minlon),
     382                    Double.parseDouble(maxlat), Double.parseDouble(maxlon));
     383            if (bounds.isOutOfTheWorld()) {
     384                Bounds copy = new Bounds(bounds);
     385                bounds.normalize();
     386                Logging.info("Bbox " + copy + " is out of the world, normalized to " + bounds);
     387            }
     388            ds.addDataSource(new DataSource(bounds, origin));
     389        } else {
     390            throw new IllegalDataException(tr("Missing mandatory attributes on element ''bounds''. " +
     391                    "Got minlon=''{0}'',minlat=''{1}'',maxlon=''{2}'',maxlat=''{3}'', origin=''{4}''.",
     392                    minlon, minlat, maxlon, maxlat, origin
     393            ));
     394        }
     395    }
     396
     397    protected final void parseId(PrimitiveData current, long id) throws IllegalDataException {
     398        current.setId(id);
     399        if (current.getUniqueId() == 0) {
     400            throw new IllegalDataException(tr("Illegal object with ID=0."));
     401        }
     402    }
     403
     404    protected final void parseTimestamp(PrimitiveData current, String time) {
     405        if (time != null && !time.isEmpty()) {
     406            current.setRawTimestamp((int) (DateUtils.tsFromString(time)/1000));
     407        }
     408    }
     409
     410    private static User createUser(String uid, String name) throws IllegalDataException {
     411        if (uid == null) {
     412            if (name == null)
     413                return null;
     414            return User.createLocalUser(name);
     415        }
     416        try {
     417            return User.createOsmUser(Long.parseLong(uid), name);
     418        } catch (NumberFormatException e) {
     419            throw new IllegalDataException(MessageFormat.format("Illegal value for attribute ''uid''. Got ''{0}''.", uid), e);
     420        }
     421    }
     422
     423    protected final void parseUser(PrimitiveData current, String user, long uid) {
     424        current.setUser(User.createOsmUser(uid, user));
     425    }
     426
     427    protected final void parseUser(PrimitiveData current, String user, String uid) throws IllegalDataException {
     428        current.setUser(createUser(uid, user));
     429    }
     430
     431    protected final void parseVisible(PrimitiveData current, String visible) {
     432        if (visible != null) {
     433            current.setVisible(Boolean.parseBoolean(visible));
     434        }
     435    }
     436
     437    protected final void parseVersion(PrimitiveData current, String versionString) throws IllegalDataException {
     438        int version = 0;
     439        if (versionString != null) {
     440            try {
     441                version = Integer.parseInt(versionString);
     442            } catch (NumberFormatException e) {
     443                throw new IllegalDataException(
     444                        tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.",
     445                        Long.toString(current.getUniqueId()), versionString), e);
     446            }
     447            parseVersion(current, version);
     448        } else {
     449            // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6
     450            if (!current.isNew() && ds.getVersion() != null && "0.6".equals(ds.getVersion())) {
     451                throw new IllegalDataException(
     452                        tr("Missing attribute ''version'' on OSM primitive with ID {0}.", Long.toString(current.getUniqueId())));
     453            }
     454        }
     455    }
     456
     457    protected final void parseVersion(PrimitiveData current, int version) throws IllegalDataException {
     458        switch (ds.getVersion()) {
     459        case "0.6":
     460            if (version <= 0 && !current.isNew()) {
     461                throw new IllegalDataException(
     462                        tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.",
     463                        Long.toString(current.getUniqueId()), version));
     464            } else if (version < 0 && current.isNew()) {
     465                Logging.warn(tr("Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.",
     466                        current.getUniqueId(), version, 0, "0.6"));
     467                version = 0;
     468            }
     469            break;
     470        default:
     471            // should not happen. API version has been checked before
     472            throw new IllegalDataException(tr("Unknown or unsupported API version. Got {0}.", ds.getVersion()));
     473        }
     474        current.setVersion(version);
     475    }
     476
     477    protected final void parseAction(PrimitiveData current, String action) {
     478        if (action == null) {
     479            // do nothing
     480        } else if ("delete".equals(action)) {
     481            current.setDeleted(true);
     482            current.setModified(current.isVisible());
     483        } else if ("modify".equals(action)) {
     484            current.setModified(true);
     485        }
     486    }
     487
     488    private static void handleIllegalChangeset(PrimitiveData current, IllegalArgumentException e, Object v)
     489            throws IllegalDataException {
     490        Logging.debug(e.getMessage());
     491        if (current.isNew()) {
     492            // for a new primitive we just log a warning
     493            Logging.info(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.",
     494                    v, current.getUniqueId()));
     495            current.setChangesetId(0);
     496        } else {
     497            // for an existing primitive this is a problem
     498            throw new IllegalDataException(tr("Illegal value for attribute ''changeset''. Got {0}.", v), e);
     499        }
     500    }
     501
     502    protected final void parseChangeset(PrimitiveData current, String v) throws IllegalDataException {
     503        if (v == null) {
     504            current.setChangesetId(0);
     505        } else {
     506            try {
     507                parseChangeset(current, Integer.parseInt(v));
     508            } catch (NumberFormatException e) {
     509                handleIllegalChangeset(current, e, v);
     510            }
     511        }
     512    }
     513
     514    protected final void parseChangeset(PrimitiveData current, int v) throws IllegalDataException {
     515        try {
     516            current.setChangesetId(v);
     517        } catch (IllegalArgumentException e) {
     518            handleIllegalChangeset(current, e, v);
     519        } catch (IllegalStateException e) {
     520            // thrown for positive changeset id on new primitives
     521            Logging.debug(e);
     522            Logging.info(e.getMessage());
     523            current.setChangesetId(0);
     524        }
     525        if (current.getChangesetId() <= 0) {
     526            if (current.isNew()) {
     527                // for a new primitive we just log a warning
     528                Logging.info(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.",
     529                        v, current.getUniqueId()));
     530                current.setChangesetId(0);
     531            } else if (current.getChangesetId() < 0) {
     532                // for an existing primitive this is a problem only for negative ids (GDPR extracts are set to 0)
     533                throw new IllegalDataException(tr("Illegal value for attribute ''changeset''. Got {0}.", v));
     534            }
     535        }
     536    }
     537
     538    protected final void parseTag(Tagged t, String key, String value) throws IllegalDataException {
     539        if (key == null || value == null) {
     540            throw new IllegalDataException(tr("Missing key or value attribute in tag."));
     541        } else if (Utils.isStripEmpty(key) && t instanceof AbstractPrimitive) {
     542            // #14199: Empty keys as ignored by AbstractPrimitive#put, but it causes problems to fix existing data
     543            // Drop the tag on import, but flag the primitive as modified
     544            ((AbstractPrimitive) t).setModified(true);
     545        } else {
     546            t.put(key.intern(), value.intern());
     547        }
     548    }
     549
     550    @FunctionalInterface
     551    protected interface CommonReader {
     552       void accept(PrimitiveData pd) throws IllegalDataException;
     553    }
     554
     555    @FunctionalInterface
     556    protected interface NodeReader {
     557       void accept(Node n) throws IllegalDataException;
     558    }
     559
     560    @FunctionalInterface
     561    protected interface WayReader {
     562       void accept(Way w, Collection<Long> nodeIds) throws IllegalDataException;
     563    }
     564
     565    @FunctionalInterface
     566    protected interface RelationReader {
     567       void accept(Relation r, Collection<RelationMemberData> members) throws IllegalDataException;
     568    }
     569
     570    private static boolean areLatLonDefined(String lat, String lon) {
     571        return lat != null && lon != null;
     572    }
     573
     574    private static boolean areLatLonDefined(double lat, double lon) {
     575        return lat != Double.NaN && lon != Double.NaN;
     576    }
     577
     578    private Node addNode(NodeData nd, NodeReader nodeReader) throws IllegalDataException {
     579        Node n = new Node(nd.getId(), nd.getVersion());
     580        n.setVisible(nd.isVisible());
     581        n.load(nd);
     582        nodeReader.accept(n);
     583        externalIdMap.put(nd.getPrimitiveId(), n);
     584        return n;
     585    }
     586
     587    protected final Node parseNode(double lat, double lon, CommonReader commonReader, NodeReader nodeReader)
     588            throws IllegalDataException {
     589        NodeData nd = new NodeData();
     590        LatLon ll = null;
     591        if (areLatLonDefined(lat, lon)) {
     592            try {
     593                ll = new LatLon(lat, lon);
     594                nd.setCoor(ll);
     595            } catch (NumberFormatException e) {
     596                Logging.trace(e);
     597            }
     598        }
     599        commonReader.accept(nd);
     600        if (areLatLonDefined(lat, lon) && (ll == null || !ll.isValid())) {
     601            throw new IllegalDataException(tr("Illegal value for attributes ''lat'', ''lon'' on node with ID {0}. Got ''{1}'', ''{2}''.",
     602                    Long.toString(nd.getId()), lat, lon));
     603        }
     604        return addNode(nd, nodeReader);
     605    }
     606
     607    protected final Node parseNode(String lat, String lon, CommonReader commonReader, NodeReader nodeReader)
     608            throws IllegalDataException {
     609        NodeData nd = new NodeData();
     610        LatLon ll = null;
     611        if (areLatLonDefined(lat, lon)) {
     612            try {
     613                ll = new LatLon(Double.parseDouble(lat), Double.parseDouble(lon));
     614                nd.setCoor(ll);
     615            } catch (NumberFormatException e) {
     616                Logging.trace(e);
     617            }
     618        }
     619        commonReader.accept(nd);
     620        if (areLatLonDefined(lat, lon) && (ll == null || !ll.isValid())) {
     621            throw new IllegalDataException(tr("Illegal value for attributes ''lat'', ''lon'' on node with ID {0}. Got ''{1}'', ''{2}''.",
     622                    Long.toString(nd.getId()), lat, lon));
     623        }
     624        return addNode(nd, nodeReader);
     625    }
     626
     627    protected final Way parseWay(CommonReader commonReader, WayReader wayReader) throws IllegalDataException {
     628        WayData wd = new WayData();
     629        commonReader.accept(wd);
     630        Way w = new Way(wd.getId(), wd.getVersion());
     631        w.setVisible(wd.isVisible());
     632        w.load(wd);
     633        externalIdMap.put(wd.getPrimitiveId(), w);
     634
     635        Collection<Long> nodeIds = new ArrayList<>();
     636        wayReader.accept(w, nodeIds);
     637        if (w.isDeleted() && !nodeIds.isEmpty()) {
     638            Logging.info(tr("Deleted way {0} contains nodes", Long.toString(w.getUniqueId())));
     639            nodeIds = new ArrayList<>();
     640        }
     641        ways.put(wd.getUniqueId(), nodeIds);
     642        return w;
     643    }
     644
     645    protected final Relation parseRelation(CommonReader commonReader, RelationReader relationReader) throws IllegalDataException {
     646        RelationData rd = new RelationData();
     647        commonReader.accept(rd);
     648        Relation r = new Relation(rd.getId(), rd.getVersion());
     649        r.setVisible(rd.isVisible());
     650        r.load(rd);
     651        externalIdMap.put(rd.getPrimitiveId(), r);
     652
     653        Collection<RelationMemberData> members = new ArrayList<>();
     654        relationReader.accept(r, members);
     655        if (r.isDeleted() && !members.isEmpty()) {
     656            Logging.info(tr("Deleted relation {0} contains members", Long.toString(r.getUniqueId())));
     657            members = new ArrayList<>();
     658        }
     659        relations.put(rd.getUniqueId(), members);
     660        return r;
     661    }
     662
     663    protected final RelationMemberData parseRelationMember(Relation r, String ref, String type, String role) throws IllegalDataException {
     664        if (ref == null) {
     665            throw new IllegalDataException(tr("Missing attribute ''ref'' on member in relation {0}.",
     666                    Long.toString(r.getUniqueId())));
     667        }
     668        try {
     669            return parseRelationMember(r, Long.parseLong(ref), type, role);
     670        } catch (NumberFormatException e) {
     671            throw new IllegalDataException(tr("Illegal value for attribute ''ref'' on member in relation {0}. Got {1}",
     672                    Long.toString(r.getUniqueId()), ref), e);
     673        }
     674    }
     675
     676    protected final RelationMemberData parseRelationMember(Relation r, long id, String type, String role) throws IllegalDataException {
     677        if (id == 0) {
     678            throw new IllegalDataException(tr("Incomplete <member> specification with ref=0"));
     679        }
     680        if (type == null) {
     681            throw new IllegalDataException(tr("Missing attribute ''type'' on member {0} in relation {1}.",
     682                    Long.toString(id), Long.toString(r.getUniqueId())));
     683        }
     684        try {
     685            return new RelationMemberData(role, OsmPrimitiveType.fromApiTypeName(type), id);
     686        } catch (IllegalArgumentException e) {
     687            throw new IllegalDataException(tr("Illegal value for attribute ''type'' on member {0} in relation {1}. Got {2}.",
     688                    Long.toString(id), Long.toString(r.getUniqueId()), type), e);
     689        }
     690    }
    209691}
  • trunk/src/org/openstreetmap/josm/io/OsmReader.java

    r14078 r14086  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    6 import java.io.IOException;
    76import java.io.InputStream;
    8 import java.io.InputStreamReader;
    9 import java.text.MessageFormat;
    10 import java.util.ArrayList;
    117import java.util.Collection;
    12 import java.util.List;
    138import java.util.Objects;
    14 import java.util.function.Consumer;
    159import java.util.regex.Matcher;
    1610import java.util.regex.Pattern;
     
    2115import javax.xml.stream.XMLStreamReader;
    2216
    23 import org.openstreetmap.josm.data.Bounds;
    24 import org.openstreetmap.josm.data.DataSource;
    25 import org.openstreetmap.josm.data.coor.LatLon;
    26 import org.openstreetmap.josm.data.osm.AbstractPrimitive;
    2717import org.openstreetmap.josm.data.osm.Changeset;
    2818import org.openstreetmap.josm.data.osm.DataSet;
    29 import org.openstreetmap.josm.data.osm.DownloadPolicy;
    3019import org.openstreetmap.josm.data.osm.Node;
    31 import org.openstreetmap.josm.data.osm.NodeData;
    32 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    3320import org.openstreetmap.josm.data.osm.PrimitiveData;
    3421import org.openstreetmap.josm.data.osm.Relation;
    35 import org.openstreetmap.josm.data.osm.RelationData;
    3622import org.openstreetmap.josm.data.osm.RelationMemberData;
    3723import org.openstreetmap.josm.data.osm.Tagged;
    38 import org.openstreetmap.josm.data.osm.UploadPolicy;
    39 import org.openstreetmap.josm.data.osm.User;
    4024import org.openstreetmap.josm.data.osm.Way;
    41 import org.openstreetmap.josm.data.osm.WayData;
    4225import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    4326import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    44 import org.openstreetmap.josm.tools.CheckParameterUtil;
    4527import org.openstreetmap.josm.tools.Logging;
    4628import org.openstreetmap.josm.tools.UncheckedParseException;
    47 import org.openstreetmap.josm.tools.Utils;
    4829import org.openstreetmap.josm.tools.XmlUtils;
    49 import org.openstreetmap.josm.tools.date.DateUtils;
    5030
    5131/**
    52  * Parser for the Osm Api. Read from an input stream and construct a dataset out of it.
     32 * Parser for the Osm API (XML output). Read from an input stream and construct a dataset out of it.
    5333 *
    5434 * For each xml element, there is a dedicated method.
     
    5939
    6040    protected XMLStreamReader parser;
    61 
    62     protected boolean cancel;
    63 
    64     /** Used by plugins to register themselves as data postprocessors. */
    65     private static volatile List<OsmServerReadPostprocessor> postprocessors;
    66 
    67     /** Register a new postprocessor.
    68      * @param pp postprocessor
    69      * @see #deregisterPostprocessor
    70      */
    71     public static void registerPostprocessor(OsmServerReadPostprocessor pp) {
    72         if (postprocessors == null) {
    73             postprocessors = new ArrayList<>();
    74         }
    75         postprocessors.add(pp);
    76     }
    77 
    78     /**
    79      * Deregister a postprocessor previously registered with {@link #registerPostprocessor}.
    80      * @param pp postprocessor
    81      * @see #registerPostprocessor
    82      */
    83     public static void deregisterPostprocessor(OsmServerReadPostprocessor pp) {
    84         if (postprocessors != null) {
    85             postprocessors.remove(pp);
    86         }
    87     }
    8841
    8942    /**
     
    9851    protected void setParser(XMLStreamReader parser) {
    9952        this.parser = parser;
     53    }
     54
     55    protected void throwException(Throwable th) throws XMLStreamException {
     56        throw new XmlStreamParsingException(th.getMessage(), parser.getLocation(), th);
    10057    }
    10158
     
    13390
    13491    private void parseOsm() throws XMLStreamException {
    135         String v = parser.getAttributeValue(null, "version");
    136         if (v == null) {
    137             throwException(tr("Missing mandatory attribute ''{0}''.", "version"));
    138         }
    139         if (!"0.6".equals(v)) {
    140             throwException(tr("Unsupported version: {0}", v));
    141         }
    142         ds.setVersion(v);
    143         parsePolicy("download", policy -> ds.setDownloadPolicy(DownloadPolicy.of(policy)));
    144         parsePolicy("upload", policy -> ds.setUploadPolicy(UploadPolicy.of(policy)));
    145         if ("true".equalsIgnoreCase(parser.getAttributeValue(null, "locked"))) {
    146             ds.lock();
     92        try {
     93            parseVersion(parser.getAttributeValue(null, "version"));
     94            parseDownloadPolicy("download", parser.getAttributeValue(null, "download"));
     95            parseUploadPolicy("upload", parser.getAttributeValue(null, "upload"));
     96            parseLocked(parser.getAttributeValue(null, "locked"));
     97        } catch (IllegalDataException e) {
     98            throwException(e);
    14799        }
    148100        String generator = parser.getAttributeValue(null, "generator");
     
    181133            } else if (event == XMLStreamConstants.END_ELEMENT) {
    182134                return;
    183             }
    184         }
    185     }
    186 
    187     private void parsePolicy(String key, Consumer<String> consumer) throws XMLStreamException {
    188         String policy = parser.getAttributeValue(null, key);
    189         if (policy != null) {
    190             try {
    191                 consumer.accept(policy);
    192             } catch (IllegalArgumentException e) {
    193                 throwException(MessageFormat.format("Illegal value for attribute ''{0}''. Got ''{1}''.", key, policy), e);
    194135            }
    195136        }
     
    202143        String maxlat = parser.getAttributeValue(null, "maxlat");
    203144        String origin = parser.getAttributeValue(null, "origin");
    204         if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
    205             if (origin == null) {
    206                 origin = generator;
    207             }
    208             Bounds bounds = new Bounds(
    209                     Double.parseDouble(minlat), Double.parseDouble(minlon),
    210                     Double.parseDouble(maxlat), Double.parseDouble(maxlon));
    211             if (bounds.isOutOfTheWorld()) {
    212                 Bounds copy = new Bounds(bounds);
    213                 bounds.normalize();
    214                 Logging.info("Bbox " + copy + " is out of the world, normalized to " + bounds);
    215             }
    216             DataSource src = new DataSource(bounds, origin);
    217             ds.addDataSource(src);
    218         } else {
    219             throwException(tr("Missing mandatory attributes on element ''bounds''. " +
    220                     "Got minlon=''{0}'',minlat=''{1}'',maxlon=''{2}'',maxlat=''{3}'', origin=''{4}''.",
    221                     minlon, minlat, maxlon, maxlat, origin
    222             ));
     145        try {
     146            parseBounds(generator, minlon, minlat, maxlon, maxlat, origin);
     147        } catch (IllegalDataException e) {
     148            throwException(e);
    223149        }
    224150        jumpToEnd();
     
    226152
    227153    protected Node parseNode() throws XMLStreamException {
    228         NodeData nd = new NodeData();
    229154        String lat = parser.getAttributeValue(null, "lat");
    230155        String lon = parser.getAttributeValue(null, "lon");
    231         LatLon ll = null;
    232         if (lat != null && lon != null) {
    233             try {
    234                 ll = new LatLon(Double.parseDouble(lat), Double.parseDouble(lon));
    235                 nd.setCoor(ll);
    236             } catch (NumberFormatException e) {
    237                 Logging.trace(e);
    238             }
    239         }
    240         readCommon(nd);
    241         if (lat != null && lon != null && (ll == null || !ll.isValid())) {
    242             throwException(tr("Illegal value for attributes ''lat'', ''lon'' on node with ID {0}. Got ''{1}'', ''{2}''.",
    243                     Long.toString(nd.getId()), lat, lon));
    244         }
    245         Node n = new Node(nd.getId(), nd.getVersion());
    246         n.setVisible(nd.isVisible());
    247         n.load(nd);
    248         externalIdMap.put(nd.getPrimitiveId(), n);
    249         while (true) {
    250             int event = parser.next();
    251             if (event == XMLStreamConstants.START_ELEMENT) {
    252                 if ("tag".equals(parser.getLocalName())) {
    253                     parseTag(n);
    254                 } else {
    255                     parseUnknown();
     156        try {
     157            return parseNode(lat, lon, this::readCommon, this::parseNodeTags);
     158        } catch (IllegalDataException e) {
     159            throwException(e);
     160        }
     161        return null;
     162    }
     163
     164    private void parseNodeTags(Node n) throws IllegalDataException {
     165        try {
     166            while (parser.hasNext()) {
     167                int event = parser.next();
     168                if (event == XMLStreamConstants.START_ELEMENT) {
     169                    if ("tag".equals(parser.getLocalName())) {
     170                        parseTag(n);
     171                    } else {
     172                        parseUnknown();
     173                    }
     174                } else if (event == XMLStreamConstants.END_ELEMENT) {
     175                    return;
    256176                }
    257             } else if (event == XMLStreamConstants.END_ELEMENT)
    258                 return n;
     177            }
     178        } catch (XMLStreamException e) {
     179            throw new IllegalDataException(e);
    259180        }
    260181    }
    261182
    262183    protected Way parseWay() throws XMLStreamException {
    263         WayData wd = new WayData();
    264         readCommon(wd);
    265         Way w = new Way(wd.getId(), wd.getVersion());
    266         w.setVisible(wd.isVisible());
    267         w.load(wd);
    268         externalIdMap.put(wd.getPrimitiveId(), w);
    269 
    270         Collection<Long> nodeIds = new ArrayList<>();
    271         while (true) {
    272             int event = parser.next();
    273             if (event == XMLStreamConstants.START_ELEMENT) {
    274                 switch (parser.getLocalName()) {
    275                 case "nd":
    276                     nodeIds.add(parseWayNode(w));
    277                     break;
    278                 case "tag":
    279                     parseTag(w);
    280                     break;
    281                 default:
    282                     parseUnknown();
     184        try {
     185            return parseWay(this::readCommon, this::parseWayNodesAndTags);
     186        } catch (IllegalDataException e) {
     187            throwException(e);
     188        }
     189        return null;
     190    }
     191
     192    private void parseWayNodesAndTags(Way w, Collection<Long> nodeIds) throws IllegalDataException {
     193        try {
     194            while (parser.hasNext()) {
     195                int event = parser.next();
     196                if (event == XMLStreamConstants.START_ELEMENT) {
     197                    switch (parser.getLocalName()) {
     198                    case "nd":
     199                        nodeIds.add(parseWayNode(w));
     200                        break;
     201                    case "tag":
     202                        parseTag(w);
     203                        break;
     204                    default:
     205                        parseUnknown();
     206                    }
     207                } else if (event == XMLStreamConstants.END_ELEMENT) {
     208                    break;
    283209                }
    284             } else if (event == XMLStreamConstants.END_ELEMENT) {
    285                 break;
    286             }
    287         }
    288         if (w.isDeleted() && !nodeIds.isEmpty()) {
    289             Logging.info(tr("Deleted way {0} contains nodes", Long.toString(w.getUniqueId())));
    290             nodeIds = new ArrayList<>();
    291         }
    292         ways.put(wd.getUniqueId(), nodeIds);
    293         return w;
     210            }
     211        } catch (XMLStreamException e) {
     212            throw new IllegalDataException(e);
     213        }
    294214    }
    295215
     
    311231
    312232    protected Relation parseRelation() throws XMLStreamException {
    313         RelationData rd = new RelationData();
    314         readCommon(rd);
    315         Relation r = new Relation(rd.getId(), rd.getVersion());
    316         r.setVisible(rd.isVisible());
    317         r.load(rd);
    318         externalIdMap.put(rd.getPrimitiveId(), r);
    319 
    320         Collection<RelationMemberData> members = new ArrayList<>();
    321         while (true) {
    322             int event = parser.next();
    323             if (event == XMLStreamConstants.START_ELEMENT) {
    324                 switch (parser.getLocalName()) {
    325                 case "member":
    326                     members.add(parseRelationMember(r));
    327                     break;
    328                 case "tag":
    329                     parseTag(r);
    330                     break;
    331                 default:
    332                     parseUnknown();
     233        try {
     234            return parseRelation(this::readCommon, this::parseRelationMembersAndTags);
     235        } catch (IllegalDataException e) {
     236            throw new XMLStreamException(e);
     237        }
     238    }
     239
     240    private void parseRelationMembersAndTags(Relation r, Collection<RelationMemberData> members) throws IllegalDataException {
     241        try {
     242            while (parser.hasNext()) {
     243                int event = parser.next();
     244                if (event == XMLStreamConstants.START_ELEMENT) {
     245                    switch (parser.getLocalName()) {
     246                    case "member":
     247                        members.add(parseRelationMember(r));
     248                        break;
     249                    case "tag":
     250                        parseTag(r);
     251                        break;
     252                    default:
     253                        parseUnknown();
     254                    }
     255                } else if (event == XMLStreamConstants.END_ELEMENT) {
     256                    break;
    333257                }
    334             } else if (event == XMLStreamConstants.END_ELEMENT) {
    335                 break;
    336             }
    337         }
    338         if (r.isDeleted() && !members.isEmpty()) {
    339             Logging.info(tr("Deleted relation {0} contains members", Long.toString(r.getUniqueId())));
    340             members = new ArrayList<>();
    341         }
    342         relations.put(rd.getUniqueId(), members);
    343         return r;
     258            }
     259        } catch (XMLStreamException e) {
     260            throw new IllegalDataException(e);
     261        }
    344262    }
    345263
    346264    private RelationMemberData parseRelationMember(Relation r) throws XMLStreamException {
    347         OsmPrimitiveType type = null;
    348         long id = 0;
    349         String value = parser.getAttributeValue(null, "ref");
    350         if (value == null) {
    351             throwException(tr("Missing attribute ''ref'' on member in relation {0}.", Long.toString(r.getUniqueId())));
    352         }
    353         try {
    354             id = Long.parseLong(value);
    355         } catch (NumberFormatException e) {
    356             throwException(tr("Illegal value for attribute ''ref'' on member in relation {0}. Got {1}", Long.toString(r.getUniqueId()),
    357                     value), e);
    358         }
    359         value = parser.getAttributeValue(null, "type");
    360         if (value == null) {
    361             throwException(tr("Missing attribute ''type'' on member {0} in relation {1}.", Long.toString(id), Long.toString(r.getUniqueId())));
    362         }
    363         try {
    364             type = OsmPrimitiveType.fromApiTypeName(value);
    365         } catch (IllegalArgumentException e) {
    366             throwException(tr("Illegal value for attribute ''type'' on member {0} in relation {1}. Got {2}.",
    367                     Long.toString(id), Long.toString(r.getUniqueId()), value), e);
    368         }
    369         String role = parser.getAttributeValue(null, "role");
    370 
    371         if (id == 0) {
    372             throwException(tr("Incomplete <member> specification with ref=0"));
    373         }
    374         jumpToEnd();
    375         return new RelationMemberData(role, type, id);
     265        RelationMemberData result = null;
     266        try {
     267            String ref = parser.getAttributeValue(null, "ref");
     268            String type = parser.getAttributeValue(null, "type");
     269            String role = parser.getAttributeValue(null, "role");
     270            result = parseRelationMember(r, ref, type, role);
     271            jumpToEnd();
     272        } catch (IllegalDataException e) {
     273            throwException(e);
     274        }
     275        return result;
    376276    }
    377277
     
    404304        String key = parser.getAttributeValue(null, "k");
    405305        String value = parser.getAttributeValue(null, "v");
    406         if (key == null || value == null) {
    407             throwException(tr("Missing key or value attribute in tag."));
    408         } else if (Utils.isStripEmpty(key) && t instanceof AbstractPrimitive) {
    409             // #14199: Empty keys as ignored by AbstractPrimitive#put, but it causes problems to fix existing data
    410             // Drop the tag on import, but flag the primitive as modified
    411             ((AbstractPrimitive) t).setModified(true);
    412         } else {
    413             t.put(key.intern(), value.intern());
     306        try {
     307            parseTag(t, key, value);
     308        } catch (IllegalDataException e) {
     309            throwException(e);
    414310        }
    415311        jumpToEnd();
     
    460356    }
    461357
    462     private User createUser(String uid, String name) throws XMLStreamException {
    463         if (uid == null) {
    464             if (name == null)
    465                 return null;
    466             return User.createLocalUser(name);
    467         }
    468         try {
    469             long id = Long.parseLong(uid);
    470             return User.createOsmUser(id, name);
    471         } catch (NumberFormatException e) {
    472             throwException(MessageFormat.format("Illegal value for attribute ''uid''. Got ''{0}''.", uid), e);
    473         }
    474         return null;
    475     }
    476 
    477358    /**
    478359     * Read out the common attributes and put them into current OsmPrimitive.
    479360     * @param current primitive to update
    480      * @throws XMLStreamException if there is an error processing the underlying XML source
     361     * @throws IllegalDataException if there is an error processing the underlying XML source
    481362     */
    482     private void readCommon(PrimitiveData current) throws XMLStreamException {
    483         current.setId(getLong("id"));
    484         if (current.getUniqueId() == 0) {
    485             throwException(tr("Illegal object with ID=0."));
    486         }
    487 
    488         String time = parser.getAttributeValue(null, "timestamp");
    489         if (time != null && !time.isEmpty()) {
    490             current.setRawTimestamp((int) (DateUtils.tsFromString(time)/1000));
    491         }
    492 
    493         String user = parser.getAttributeValue(null, "user");
    494         String uid = parser.getAttributeValue(null, "uid");
    495         current.setUser(createUser(uid, user));
    496 
    497         String visible = parser.getAttributeValue(null, "visible");
    498         if (visible != null) {
    499             current.setVisible(Boolean.parseBoolean(visible));
    500         }
    501 
    502         String versionString = parser.getAttributeValue(null, "version");
    503         int version = 0;
    504         if (versionString != null) {
    505             try {
    506                 version = Integer.parseInt(versionString);
    507             } catch (NumberFormatException e) {
    508                 throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.",
    509                         Long.toString(current.getUniqueId()), versionString), e);
    510             }
    511             switch (ds.getVersion()) {
    512             case "0.6":
    513                 if (version <= 0 && !current.isNew()) {
    514                     throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.",
    515                             Long.toString(current.getUniqueId()), versionString));
    516                 } else if (version < 0 && current.isNew()) {
    517                     Logging.warn(tr("Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.",
    518                             current.getUniqueId(), version, 0, "0.6"));
    519                     version = 0;
    520                 }
    521                 break;
    522             default:
    523                 // should not happen. API version has been checked before
    524                 throwException(tr("Unknown or unsupported API version. Got {0}.", ds.getVersion()));
    525             }
    526         } else {
    527             // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6
    528             if (!current.isNew() && ds.getVersion() != null && "0.6".equals(ds.getVersion())) {
    529                 throwException(tr("Missing attribute ''version'' on OSM primitive with ID {0}.", Long.toString(current.getUniqueId())));
    530             }
    531         }
    532         current.setVersion(version);
    533 
    534         String action = parser.getAttributeValue(null, "action");
    535         if (action == null) {
    536             // do nothing
    537         } else if ("delete".equals(action)) {
    538             current.setDeleted(true);
    539             current.setModified(current.isVisible());
    540         } else if ("modify".equals(action)) {
    541             current.setModified(true);
    542         }
    543 
    544         String v = parser.getAttributeValue(null, "changeset");
    545         if (v == null) {
    546             current.setChangesetId(0);
    547         } else {
    548             try {
    549                 current.setChangesetId(Integer.parseInt(v));
    550             } catch (IllegalArgumentException e) {
    551                 Logging.debug(e.getMessage());
    552                 if (current.isNew()) {
    553                     // for a new primitive we just log a warning
    554                     Logging.info(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.",
    555                             v, current.getUniqueId()));
    556                     current.setChangesetId(0);
    557                 } else {
    558                     // for an existing primitive this is a problem
    559                     throwException(tr("Illegal value for attribute ''changeset''. Got {0}.", v), e);
    560                 }
    561             } catch (IllegalStateException e) {
    562                 // thrown for positive changeset id on new primitives
    563                 Logging.debug(e);
    564                 Logging.info(e.getMessage());
    565                 current.setChangesetId(0);
    566             }
    567             if (current.getChangesetId() <= 0) {
    568                 if (current.isNew()) {
    569                     // for a new primitive we just log a warning
    570                     Logging.info(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.",
    571                             v, current.getUniqueId()));
    572                     current.setChangesetId(0);
    573                 } else if (current.getChangesetId() < 0) {
    574                     // for an existing primitive this is a problem only for negative ids (GDPR extracts are set to 0)
    575                     throwException(tr("Illegal value for attribute ''changeset''. Got {0}.", v));
    576                 }
    577             }
     363    private void readCommon(PrimitiveData current) throws IllegalDataException {
     364        try {
     365            parseId(current, getLong("id"));
     366            parseTimestamp(current, parser.getAttributeValue(null, "timestamp"));
     367            parseUser(current, parser.getAttributeValue(null, "user"), parser.getAttributeValue(null, "uid"));
     368            parseVisible(current, parser.getAttributeValue(null, "visible"));
     369            parseVersion(current, parser.getAttributeValue(null, "version"));
     370            parseAction(current, parser.getAttributeValue(null, "action"));
     371            parseChangeset(current, parser.getAttributeValue(null, "changeset"));
     372        } catch (UncheckedParseException | XMLStreamException e) {
     373            throw new IllegalDataException(e);
    578374        }
    579375    }
     
    581377    private long getLong(String name) throws XMLStreamException {
    582378        String value = parser.getAttributeValue(null, name);
    583         if (value == null) {
    584             throwException(tr("Missing required attribute ''{0}''.", name));
    585         }
    586         try {
    587             return Long.parseLong(value);
    588         } catch (NumberFormatException e) {
    589             throwException(tr("Illegal long value for attribute ''{0}''. Got ''{1}''.", name, value), e);
     379        try {
     380            return getLong(name, value);
     381        } catch (IllegalDataException e) {
     382            throwException(e);
    590383        }
    591384        return 0; // should not happen
     
    608401    @Override
    609402    protected DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
    610         if (progressMonitor == null) {
    611             progressMonitor = NullProgressMonitor.INSTANCE;
    612         }
    613         ProgressMonitor.CancelListener cancelListener = () -> cancel = true;
    614         progressMonitor.addCancelListener(cancelListener);
    615         CheckParameterUtil.ensureParameterNotNull(source, "source");
    616         try {
    617             progressMonitor.beginTask(tr("Prepare OSM data...", 2));
    618             progressMonitor.indeterminateSubTask(tr("Parsing OSM data..."));
    619 
    620             try (InputStreamReader ir = UTFInputStreamReader.create(source)) {
     403        return doParseDataSet(source, progressMonitor, ir -> {
     404            try {
    621405                setParser(XmlUtils.newSafeXMLInputFactory().createXMLStreamReader(ir));
    622406                parse();
    623             }
    624             progressMonitor.worked(1);
    625 
    626             boolean readOnly = getDataSet().isLocked();
    627 
    628             progressMonitor.indeterminateSubTask(tr("Preparing data set..."));
    629             if (readOnly) {
    630                 getDataSet().unlock();
    631             }
    632             prepareDataSet();
    633             if (readOnly) {
    634                 getDataSet().lock();
    635             }
    636             progressMonitor.worked(1);
    637 
    638             // iterate over registered postprocessors and give them each a chance
    639             // to modify the dataset we have just loaded.
    640             if (postprocessors != null) {
    641                 for (OsmServerReadPostprocessor pp : postprocessors) {
    642                     pp.postprocessDataSet(getDataSet(), progressMonitor);
     407            } catch (XmlStreamParsingException | UncheckedParseException e) {
     408                throw new IllegalDataException(e.getMessage(), e);
     409            } catch (XMLStreamException e) {
     410                String msg = e.getMessage();
     411                Pattern p = Pattern.compile("Message: (.+)");
     412                Matcher m = p.matcher(msg);
     413                if (m.find()) {
     414                    msg = m.group(1);
    643415                }
    644             }
    645             // Make sure postprocessors did not change the read-only state
    646             if (readOnly && !getDataSet().isLocked()) {
    647                 getDataSet().lock();
    648             }
    649             return getDataSet();
    650         } catch (IllegalDataException e) {
    651             throw e;
    652         } catch (XmlStreamParsingException | UncheckedParseException e) {
    653             throw new IllegalDataException(e.getMessage(), e);
    654         } catch (XMLStreamException e) {
    655             String msg = e.getMessage();
    656             Pattern p = Pattern.compile("Message: (.+)");
    657             Matcher m = p.matcher(msg);
    658             if (m.find()) {
    659                 msg = m.group(1);
    660             }
    661             if (e.getLocation() != null)
    662                 throw new IllegalDataException(tr("Line {0} column {1}: ",
    663                         e.getLocation().getLineNumber(), e.getLocation().getColumnNumber()) + msg, e);
    664             else
    665                 throw new IllegalDataException(msg, e);
    666         } catch (IOException e) {
    667             throw new IllegalDataException(e);
    668         } finally {
    669             progressMonitor.finishTask();
    670             progressMonitor.removeCancelListener(cancelListener);
    671         }
     416                if (e.getLocation() != null)
     417                    throw new IllegalDataException(tr("Line {0} column {1}: ",
     418                            e.getLocation().getLineNumber(), e.getLocation().getColumnNumber()) + msg, e);
     419                else
     420                    throw new IllegalDataException(msg, e);
     421            }
     422        });
    672423    }
    673424
     
    676427     *
    677428     * @param source the source input stream. Must not be null.
    678      * @param progressMonitor  the progress monitor. If null, {@link NullProgressMonitor#INSTANCE} is assumed
     429     * @param progressMonitor the progress monitor. If null, {@link NullProgressMonitor#INSTANCE} is assumed
    679430     *
    680431     * @return the dataset with the parsed data
  • trunk/src/org/openstreetmap/josm/io/OverpassDownloadReader.java

    r14015 r14086  
    8383    }
    8484
     85    static final class OverpassOsmJsonReader extends OsmJsonReader {
     86
     87    }
     88
    8589    /**
    8690     * Possible Overpass API output format, with the {@code [out:<directive>]} statement.
     
    165169    static {
    166170        registerOverpassOutpoutFormatReader(OverpassOutpoutFormat.OSM_XML, OverpassOsmReader.class);
     171        registerOverpassOutpoutFormatReader(OverpassOutpoutFormat.OSM_JSON, OverpassOsmJsonReader.class);
    167172    }
    168173
     
    410415        return query == null ? query : query
    411416                .replaceAll("out( body| skel| ids)?( id| qt)?;", "out meta$2;")
    412                 .replaceAll("(?s)\\[out:(json|csv)[^\\]]*\\]", "[out:xml]");
     417                .replaceAll("(?s)\\[out:(csv)[^\\]]*\\]", "[out:xml]");
    413418    }
    414419}
Note: See TracChangeset for help on using the changeset viewer.