Changeset 12714 in josm for trunk


Ignore:
Timestamp:
2017-09-03T21:14:05+02:00 (7 years ago)
Author:
Don-vip
Message:

fix #15228 - Support missing Overpass Turbo extended shortcuts: {{center}}, {{date:string}}, {{geocodeId:name}}, {{geocodeBbox:name}}, {{geocodeCoords:name}}

Location:
trunk
Files:
2 edited

Legend:

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

    r12620 r12714  
    77import java.io.InputStream;
    88import java.nio.charset.StandardCharsets;
     9import java.time.Duration;
     10import java.time.LocalDateTime;
     11import java.time.Period;
     12import java.time.ZoneOffset;
    913import java.util.EnumMap;
     14import java.util.Locale;
    1015import java.util.Map;
    1116import java.util.NoSuchElementException;
     
    2126import org.openstreetmap.josm.data.Bounds;
    2227import org.openstreetmap.josm.data.DataSource;
     28import org.openstreetmap.josm.data.coor.LatLon;
     29import org.openstreetmap.josm.data.osm.BBox;
    2330import org.openstreetmap.josm.data.osm.DataSet;
    2431import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    2532import org.openstreetmap.josm.data.osm.PrimitiveId;
    2633import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     34import org.openstreetmap.josm.io.NameFinder.SearchResult;
    2735import org.openstreetmap.josm.tools.HttpClient;
    2836import org.openstreetmap.josm.tools.Logging;
     
    146154            return super.getRequestForBbox(lon1, lat1, lon2, lat2);
    147155        else {
    148             final String query = this.overpassQuery.replace("{{bbox}}", lat1 + "," + lon1 + "," + lat2 + "," + lon2);
     156            final String query = this.overpassQuery
     157                    .replace("{{bbox}}", bbox(lon1, lat1, lon2, lat2))
     158                    .replace("{{center}}", center(lon1, lat1, lon2, lat2));
    149159            final String expandedOverpassQuery = expandExtendedQueries(query);
    150160            return "interpreter" + DATA_PREFIX + Utils.encodeUrl(expandedOverpassQuery);
     
    160170    static String expandExtendedQueries(String query) {
    161171        final StringBuffer sb = new StringBuffer();
    162         final Matcher matcher = Pattern.compile("\\{\\{(geocodeArea):([^}]+)\\}\\}").matcher(query);
     172        final Matcher matcher = Pattern.compile("\\{\\{(date|geocodeArea|geocodeBbox|geocodeCoords|geocodeId):([^}]+)\\}\\}").matcher(query);
    163173        while (matcher.find()) {
    164174            try {
    165175                switch (matcher.group(1)) {
     176                    case "date":
     177                        matcher.appendReplacement(sb, date(matcher.group(2), LocalDateTime.now()));
     178                        break;
    166179                    case "geocodeArea":
    167180                        matcher.appendReplacement(sb, geocodeArea(matcher.group(2)));
    168181                        break;
     182                    case "geocodeBbox":
     183                        matcher.appendReplacement(sb, geocodeBbox(matcher.group(2)));
     184                        break;
     185                    case "geocodeCoords":
     186                        matcher.appendReplacement(sb, geocodeCoords(matcher.group(2)));
     187                        break;
     188                    case "geocodeId":
     189                        matcher.appendReplacement(sb, geocodeId(matcher.group(2)));
     190                        break;
    169191                    default:
    170192                        Logging.warn("Unsupported syntax: " + matcher.group(1));
    171193                }
    172             } catch (UncheckedParseException ex) {
     194            } catch (UncheckedParseException | IOException | NoSuchElementException | IndexOutOfBoundsException ex) {
    173195                final String msg = tr("Failed to evaluate {0}", matcher.group());
    174196                Logging.log(Logging.LEVEL_WARN, msg, ex);
     
    180202    }
    181203
    182     private static String geocodeArea(String area) {
     204    static String bbox(double lon1, double lat1, double lon2, double lat2) {
     205        return lat1 + "," + lon1 + "," + lat2 + "," + lon2;
     206    }
     207
     208    static String center(double lon1, double lat1, double lon2, double lat2) {
     209        LatLon c = new BBox(lon1, lat1, lon2, lat2).getCenter();
     210        return c.lat()+ "," + c.lon();
     211    }
     212
     213    static String date(String humanDuration, LocalDateTime from) {
     214        // Convert to ISO 8601. Replace months by X temporarily to avoid conflict with minutes
     215        String duration = humanDuration.toLowerCase(Locale.ENGLISH).replace(" ", "")
     216                .replaceAll("years?", "Y").replaceAll("months?", "X").replaceAll("weeks?", "W")
     217                .replaceAll("days?", "D").replaceAll("hours?", "H").replaceAll("minutes?", "M").replaceAll("seconds?", "S");
     218        Matcher matcher = Pattern.compile(
     219                "((?:[0-9]+Y)?(?:[0-9]+X)?(?:[0-9]+W)?)"+
     220                "((?:[0-9]+D)?)" +
     221                "((?:[0-9]+H)?(?:[0-9]+M)?(?:[0-9]+(?:[.,][0-9]{0,9})?S)?)?").matcher(duration);
     222        boolean javaPer = false;
     223        boolean javaDur = false;
     224        if (matcher.matches()) {
     225            javaPer = matcher.group(1) != null && !matcher.group(1).isEmpty();
     226            javaDur = matcher.group(3) != null && !matcher.group(3).isEmpty();
     227            duration = 'P' + matcher.group(1).replace('X', 'M') + matcher.group(2);
     228            if (javaDur) {
     229                duration += 'T' + matcher.group(3);
     230            }
     231        }
     232
     233        // Duration is now a full ISO 8601 duration string. Unfortunately Java does not allow to parse it entirely.
     234        // We must split the "period" (years, months, weeks, days) from the "duration" (days, hours, minutes, seconds).
     235        Period p = null;
     236        Duration d = null;
     237        int idx = duration.indexOf('T');
     238        if (javaPer) {
     239            p = Period.parse(javaDur ? duration.substring(0, idx) : duration);
     240        }
     241        if (javaDur) {
     242            d = Duration.parse(javaPer ? 'P' + duration.substring(idx, duration.length()) : duration);
     243        } else if (!javaPer) {
     244            d = Duration.parse(duration);
     245        }
     246
     247        // Now that period and duration are known, compute the correct date/time
     248        LocalDateTime dt = from;
     249        if (p != null) {
     250            dt = dt.minus(p);
     251        }
     252        if (d != null) {
     253            dt = dt.minus(d);
     254        }
     255
     256        // Returns the date/time formatted in ISO 8601
     257        return dt.toInstant(ZoneOffset.UTC).toString();
     258    }
     259
     260    private static SearchResult searchName(String area) throws IOException {
     261        return NameFinder.queryNominatim(area).stream().filter(
     262                x -> !OsmPrimitiveType.NODE.equals(x.getOsmId().getType())).iterator().next();
     263    }
     264
     265    static String geocodeArea(String area) throws IOException {
    183266        // Offsets defined in https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#By_element_id
    184267        final EnumMap<OsmPrimitiveType, Long> idOffset = new EnumMap<>(OsmPrimitiveType.class);
     
    186269        idOffset.put(OsmPrimitiveType.WAY, 2_400_000_000L);
    187270        idOffset.put(OsmPrimitiveType.RELATION, 3_600_000_000L);
    188         try {
    189             final PrimitiveId osmId = NameFinder.queryNominatim(area).stream().filter(
    190                     x -> !OsmPrimitiveType.NODE.equals(x.getOsmId().getType())).iterator().next().getOsmId();
    191             return String.format("area(%d)", osmId.getUniqueId() + idOffset.get(osmId.getType()));
    192         } catch (IOException | NoSuchElementException | IndexOutOfBoundsException ex) {
    193             throw new UncheckedParseException(ex);
    194         }
     271        final PrimitiveId osmId = searchName(area).getOsmId();
     272        return String.format("area(%d)", osmId.getUniqueId() + idOffset.get(osmId.getType()));
     273    }
     274
     275    static String geocodeBbox(String area) throws IOException {
     276        Bounds bounds = searchName(area).getBounds();
     277        return bounds.getMinLat() + "," + bounds.getMinLon() + "," + bounds.getMaxLat() + "," + bounds.getMaxLon();
     278    }
     279
     280    static String geocodeCoords(String area) throws IOException {
     281        SearchResult result = searchName(area);
     282        return result.getLat() + "," + result.getLon();
     283    }
     284
     285    static String geocodeId(String area) throws IOException {
     286        PrimitiveId osmId = searchName(area).getOsmId();
     287        return String.format("%s(%d)", osmId.getType().getAPIName(), osmId.getUniqueId());
    195288    }
    196289
  • trunk/test/unit/org/openstreetmap/josm/io/OverpassDownloadReaderTest.java

    r12557 r12714  
    77import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
    88import static org.junit.Assert.assertEquals;
     9import static org.junit.Assert.assertNotNull;
    910import static org.junit.Assert.assertTrue;
    1011
     12import java.time.LocalDateTime;
    1113import java.util.regex.Matcher;
    1214
     
    2022import org.openstreetmap.josm.tools.OverpassTurboQueryWizard;
    2123import org.openstreetmap.josm.tools.Utils;
     24import org.openstreetmap.josm.tools.date.DateUtils;
    2225
    2326import com.github.tomakehurst.wiremock.junit.WireMockRule;
     
    8790
    8891    /**
     92     * Tests evaluating the extended query feature {@code date}.
     93     */
     94    @Test
     95    public void testDate() {
     96        LocalDateTime from = LocalDateTime.of(2017, 7, 14, 2, 40);
     97        assertEquals("2016-07-14T02:40:00Z", OverpassDownloadReader.date("1 year", from));
     98        assertEquals("2007-07-14T02:40:00Z", OverpassDownloadReader.date("10years", from));
     99        assertEquals("2017-06-14T02:40:00Z", OverpassDownloadReader.date("1 month", from));
     100        assertEquals("2016-09-14T02:40:00Z", OverpassDownloadReader.date("10months", from));
     101        assertEquals("2017-07-07T02:40:00Z", OverpassDownloadReader.date("1 week", from));
     102        assertEquals("2017-05-05T02:40:00Z", OverpassDownloadReader.date("10weeks", from));
     103        assertEquals("2017-07-13T02:40:00Z", OverpassDownloadReader.date("1 day", from));
     104        assertEquals("2017-07-04T02:40:00Z", OverpassDownloadReader.date("10days", from));
     105        assertEquals("2017-07-14T01:40:00Z", OverpassDownloadReader.date("1 hour", from));
     106        assertEquals("2017-07-13T16:40:00Z", OverpassDownloadReader.date("10hours", from));
     107        assertEquals("2017-07-14T02:39:00Z", OverpassDownloadReader.date("1 minute", from));
     108        assertEquals("2017-07-14T02:30:00Z", OverpassDownloadReader.date("10minutes", from));
     109        assertEquals("2017-07-14T02:39:59Z", OverpassDownloadReader.date("1 second", from));
     110        assertEquals("2017-07-14T02:39:50Z", OverpassDownloadReader.date("10seconds", from));
     111
     112        assertEquals("2016-07-13T02:40:00Z", OverpassDownloadReader.date("1 year 1 day", from));
     113        assertEquals("2016-07-14T02:38:20Z", OverpassDownloadReader.date("1 year 100 seconds", from));
     114        assertEquals("2017-07-13T02:38:20Z", OverpassDownloadReader.date("1 day  100 seconds", from));
     115    }
     116
     117    /**
     118     * Tests evaluating the extended query feature {@code date} through {@code newer:} operator.
     119     */
     120    @Test
     121    public void testDateNewer() {
     122        final String query = getExpandedQuery("type:node and newer:3minutes");
     123        String statement = query.substring(query.indexOf("node(newer:\"") + 12, query.lastIndexOf("\");"));
     124        assertNotNull(DateUtils.fromString(statement));
     125    }
     126
     127    /**
    89128     * Tests evaluating the extended query feature {@code geocodeArea}.
    90129     */
Note: See TracChangeset for help on using the changeset viewer.