Changeset 18711 in josm for trunk/src


Ignore:
Timestamp:
2023-04-24T19:55:08+02:00 (12 months ago)
Author:
taylor.smock
Message:

Fix #22726: DownloadOsmChange task should not always get the history for each unknown object

In many cases, the latest version of an object will also be the version we will
get when iterating through all of its history. This will always be the case for
objects with only one version. As such, in most cases, getting the latest version
of an object will significantly reduce the number of individual history calls.

In a test, the amount of time taken to load an OSC went from 105 seconds to 14
seconds. This is an 86% improvement in terms of time taken.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmChangeTask.java

    r17838 r18711  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.io.IOException;
    67import java.time.Instant;
    78import java.util.Arrays;
     
    1314import java.util.concurrent.Future;
    1415import java.util.concurrent.RejectedExecutionException;
     16import java.util.function.Function;
    1517import java.util.regex.Matcher;
     18import java.util.stream.Collectors;
    1619
    1720import org.openstreetmap.josm.data.Bounds;
     
    2427import org.openstreetmap.josm.data.osm.PrimitiveData;
    2528import org.openstreetmap.josm.data.osm.PrimitiveId;
     29import org.openstreetmap.josm.data.osm.Relation;
    2630import org.openstreetmap.josm.data.osm.RelationData;
     31import org.openstreetmap.josm.data.osm.Way;
    2732import org.openstreetmap.josm.data.osm.WayData;
    2833import org.openstreetmap.josm.data.osm.history.History;
     
    3742import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    3843import org.openstreetmap.josm.io.Compression;
     44import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
    3945import org.openstreetmap.josm.io.OsmApi;
    4046import org.openstreetmap.josm.io.OsmServerLocationReader;
     
    4349import org.openstreetmap.josm.io.UrlPatterns.OsmChangeUrlPattern;
    4450import org.openstreetmap.josm.tools.Logging;
     51import org.xml.sax.SAXException;
    4552
    4653/**
     
    105112
    106113        @Override
     114        public void realRun() throws IOException, SAXException, OsmTransferException {
     115            super.realRun();
     116            final Map<OsmPrimitive, Instant> toLoadNext = new HashMap<>();
     117            final Map<OsmPrimitive, Instant> toLoad = getToLoad(dataSet);
     118            while (!toLoad.isEmpty()) {
     119                loadLastVersions(toLoad, toLoadNext);
     120                toLoad.putAll(toLoadNext);
     121                toLoadNext.clear();
     122            }
     123        }
     124
     125        /**
     126         * This gets the last versions of references primitives. This may enough for many of the primitives.
     127         * @param toLoad The primitives to load
     128         * @param toLoadNext The primitives to load next (filled by this method)
     129         */
     130        private void loadLastVersions(Map<OsmPrimitive, Instant> toLoad, Map<OsmPrimitive, Instant> toLoadNext) throws OsmTransferException {
     131            final Map<OsmPrimitiveType, Map<OsmPrimitive, Instant>> typeMap = toLoad.entrySet().stream()
     132                    .collect(Collectors.groupingBy(entry -> entry.getKey().getType(), Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
     133            final Map<PrimitiveId, OsmPrimitive> idMap = toLoad.keySet().stream()
     134                    .collect(Collectors.toMap(OsmPrimitive::getPrimitiveId, Function.identity()));
     135            for (OsmPrimitiveType type : Arrays.asList(OsmPrimitiveType.NODE, OsmPrimitiveType.WAY, OsmPrimitiveType.RELATION)) {
     136                if (!typeMap.containsKey(type)) {
     137                    continue;
     138                }
     139                final MultiFetchServerObjectReader reader = MultiFetchServerObjectReader.create();
     140                typeMap.get(type).forEach((primitive, instant) -> reader.append(primitive));
     141                final DataSet ds = reader.parseOsm(this.progressMonitor.createSubTaskMonitor(1, false));
     142                switch (type) {
     143                    case NODE:
     144                        for (Node node : ds.getNodes()) {
     145                            Node original = (Node) idMap.get(node.getPrimitiveId());
     146                            if (original != null && toLoad.get(original).isAfter(node.getInstant())) {
     147                                original.load(node.save());
     148                            }
     149                            toLoad.remove(original);
     150                        }
     151                        break;
     152                    case WAY:
     153                        for (Way way : ds.getWays()) {
     154                            Way original = (Way) idMap.get(way.getPrimitiveId());
     155                            if (original != null && toLoad.get(original).isAfter(way.getInstant())) {
     156                                Instant date = toLoad.get(original);
     157                                original.load(way.save());
     158                                for (Long nodeId : way.getNodeIds()) {
     159                                    if (way.getDataSet().getPrimitiveById(nodeId, OsmPrimitiveType.NODE) == null) {
     160                                        Node n = new Node(nodeId);
     161                                        way.getDataSet().addPrimitive(n);
     162                                        toLoadNext.put(n, date);
     163                                    }
     164                                }
     165                            }
     166                            toLoad.remove(original);
     167                        }
     168                        break;
     169                    case RELATION:
     170                        for (Relation relation : ds.getRelations()) {
     171                            Relation original = (Relation) idMap.get(relation.getPrimitiveId());
     172                            if (original != null && toLoad.get(original).isAfter(relation.getInstant())) {
     173                                original.load(relation.save());
     174                            }
     175                            toLoad.remove(relation);
     176                        }
     177                        break;
     178                    default:
     179                        throw new IllegalStateException("Only Node, Ways, and Relations should be returned by the API");
     180                }
     181            }
     182        }
     183
     184        @Override
    107185        protected void finish() {
    108186            super.finish();
     
    112190                // A changeset does not contain all referred primitives, this is the map of incomplete ones
    113191                // For each incomplete primitive, we'll have to get its state at date it was referred
    114                 Map<OsmPrimitive, Instant> toLoad = new HashMap<>();
    115                 for (OsmPrimitive p : downloadedData.allNonDeletedPrimitives()) {
    116                     if (p.isIncomplete()) {
    117                         Instant timestamp = p.getReferrers().stream()
    118                                 .filter(ref -> !ref.isTimestampEmpty())
    119                                 .findFirst()
    120                                 .map(AbstractPrimitive::getInstant)
    121                                 .orElse(null);
    122                         toLoad.put(p, timestamp);
    123                     }
    124                 }
     192                Map<OsmPrimitive, Instant> toLoad = getToLoad(downloadedData);
    125193                if (isCanceled()) return;
    126194                // Let's load all required history
     
    131199            }
    132200        }
     201    }
     202
     203    /**
     204     * Get the primitives to load more information
     205     * @param ds The dataset to look for incomplete primitives from
     206     * @return The objects that still need to be loaded
     207     */
     208    private static Map<OsmPrimitive, Instant> getToLoad(DataSet ds) {
     209        Map<OsmPrimitive, Instant> toLoad = new HashMap<>();
     210        for (OsmPrimitive p : ds.allNonDeletedPrimitives()) {
     211            if (p.isIncomplete()) {
     212                Instant timestamp = p.getReferrers().stream()
     213                        .filter(ref -> !ref.isTimestampEmpty())
     214                        .findFirst()
     215                        .map(AbstractPrimitive::getInstant)
     216                        .orElse(null);
     217                toLoad.put(p, timestamp);
     218            }
     219        }
     220        return toLoad;
    133221    }
    134222
Note: See TracChangeset for help on using the changeset viewer.