Allow downloading of arbitrary data from the main download panel

Signed-off-by: Taylor Smock <taylor.smock@kaart.com>
pull/1/head
Taylor Smock 2019-11-25 16:54:09 -07:00
rodzic 5768540ff0
commit 38399f73cc
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 625F6A74A3E4311A
13 zmienionych plików z 594 dodań i 208 usunięć

Wyświetl plik

@ -17,15 +17,16 @@ import javax.swing.JMenu;
import javax.swing.JMenuItem;
import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.data.Version;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.MainMenu;
import org.openstreetmap.josm.gui.MapFrame;
import org.openstreetmap.josm.gui.download.DownloadDialog;
import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
import org.openstreetmap.josm.io.remotecontrol.RequestProcessor;
import org.openstreetmap.josm.plugins.Plugin;
import org.openstreetmap.josm.plugins.PluginInformation;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIAction;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIArbitraryAction;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIDataUtils;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAILayer;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIMoveAction;
@ -33,6 +34,7 @@ import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIObject;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIRemoteControl;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIUploadHook;
import org.openstreetmap.josm.plugins.mapwithai.backend.MergeDuplicateWaysAction;
import org.openstreetmap.josm.plugins.mapwithai.frontend.MapWithAIDownloadReader;
import org.openstreetmap.josm.spi.preferences.Config;
import org.openstreetmap.josm.tools.Destroyable;
import org.openstreetmap.josm.tools.Logging;
@ -46,12 +48,13 @@ public final class MapWithAIPlugin extends Plugin implements Destroyable {
private final PreferenceSetting preferenceSetting;
private final MapWithAIDownloadReader mapWithAIDownloadReader;
private final List<Destroyable> destroyables;
private static final Map<Class<? extends JosmAction>, Boolean> MENU_ENTRIES = new LinkedHashMap<>();
static {
MENU_ENTRIES.put(MapWithAIAction.class, false);
MENU_ENTRIES.put(MapWithAIArbitraryAction.class, true);
MENU_ENTRIES.put(MapWithAIMoveAction.class, false);
MENU_ENTRIES.put(MergeDuplicateWaysAction.class, true);
}
@ -87,6 +90,8 @@ public final class MapWithAIPlugin extends Plugin implements Destroyable {
destroyables = new ArrayList<>();
destroyables.add(new MapWithAIUploadHook(info));
mapFrameInitialized(null, MainApplication.getMap());
mapWithAIDownloadReader = new MapWithAIDownloadReader();
DownloadDialog.addDownloadSource(mapWithAIDownloadReader);
MainApplication.worker.execute(() -> UpdateProd.doProd(info.mainversion));
}
@ -141,12 +146,15 @@ public final class MapWithAIPlugin extends Plugin implements Destroyable {
}
MainApplication.getLayerManager().getLayersOfType(MapWithAILayer.class).stream()
.forEach(layer -> MainApplication.getLayerManager().removeLayer(layer));
.forEach(layer -> MainApplication.getLayerManager().removeLayer(layer));
if (!Config.getPref().getBoolean(PAINTSTYLE_PREEXISTS)) {
MapWithAIDataUtils.removeMapWithAIPaintStyles();
}
destroyables.forEach(Destroyable::destroy);
if (Version.getInstance().getVersion() > 15542) {
DownloadDialog.removeDownloadSource(mapWithAIDownloadReader);
}
}
}

Wyświetl plik

@ -0,0 +1,44 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.backend;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.io.InputStream;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.io.BoundingBoxDownloader;
import org.openstreetmap.josm.io.IllegalDataException;
import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
public class BoundingBoxMapWithAIDownloader extends BoundingBoxDownloader {
private final String url;
private final boolean crop;
private final Bounds downloadArea;
public BoundingBoxMapWithAIDownloader(Bounds downloadArea, String url, boolean crop) {
super(downloadArea);
this.url = url;
this.crop = crop;
this.downloadArea = downloadArea;
}
@Override
protected String getRequestForBbox(double lon1, double lat1, double lon2, double lat2) {
return url.replace("{bbox}", Double.toString(lon1) + ',' + lat1 + ',' + lon2 + ',' + lat2)
+ (crop ? "crop_bbox=" + lon1 + ',' + lat1 + ',' + lon2 + ',' + lat2 : "");
}
/**
* Returns the name of the download task to be displayed in the
* {@link ProgressMonitor}.
*
* @return task name
*/
@Override
protected String getTaskName() {
return tr("Contacting {0} Server...", MapWithAIPlugin.NAME);
}
}

Wyświetl plik

@ -0,0 +1,88 @@
package org.openstreetmap.josm.plugins.mapwithai.backend;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Future;
import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
import org.openstreetmap.josm.actions.downloadtasks.DownloadParams;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.ViewportData;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.MapFrame;
import org.openstreetmap.josm.gui.io.UpdatePrimitivesTask;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.io.OsmServerReader;
import org.openstreetmap.josm.tools.Utils;
public class DownloadMapWithAITask extends DownloadOsmTask {
@Override
public Future<?> download(OsmServerReader reader, DownloadParams settings, Bounds downloadArea,
ProgressMonitor progressMonitor) {
return download(new MapWithAIDownloadTask(settings, reader, progressMonitor, zoomAfterDownload), downloadArea);
}
@Override
public String getConfirmationMessage(URL url) {
if (url != null) {
Collection<String> items = new ArrayList<>();
items.add(tr("OSM Server URL:") + ' ' + url.getHost());
items.add(tr("Command") + ": " + url.getPath());
if (url.getQuery() != null) {
items.add(tr("Request details: {0}", url.getQuery().replaceAll(",\\s*", ", ")));
}
return Utils.joinAsHtmlUnorderedList(items);
}
return null;
}
protected class MapWithAIDownloadTask extends DownloadOsmTask.DownloadTask {
/**
* Constructs a new {@code DownloadTask}.
*
* @param settings download settings
* @param reader OSM data reader
* @param progressMonitor progress monitor
*/
public MapWithAIDownloadTask(DownloadParams settings, OsmServerReader reader, ProgressMonitor progressMonitor) {
this(settings, reader, progressMonitor, true);
}
/**
* Constructs a new {@code DownloadTask}.
*
* @param settings download settings
* @param reader OSM data reader
* @param progressMonitor progress monitor
* @param zoomAfterDownload If true, the map view will zoom to download area
* after download
*/
public MapWithAIDownloadTask(DownloadParams settings, OsmServerReader reader, ProgressMonitor progressMonitor,
boolean zoomAfterDownload) {
super(settings, reader, progressMonitor, zoomAfterDownload);
}
@Override
protected void loadData(String newLayerName, Bounds bounds) {
MapWithAILayer layer = MapWithAIDataUtils.getLayer(true);
Collection<OsmPrimitive> primitivesToUpdate = searchPrimitivesToUpdate(bounds, layer.getDataSet());
layer.mergeFrom(dataSet);
MapFrame map = MainApplication.getMap();
if (map != null && (zoomAfterDownload
|| MainApplication.getLayerManager().getLayers().parallelStream().allMatch(layer::equals))) {
computeBbox(bounds).map(ViewportData::new).ifPresent(map.mapView::zoomTo);
}
if (!primitivesToUpdate.isEmpty()) {
MainApplication.worker.execute(new UpdatePrimitivesTask(layer, primitivesToUpdate));
}
layer.onPostDownloadFromServer(bounds);
}
}
}

Wyświetl plik

@ -26,6 +26,7 @@ import javax.net.ssl.SSLException;
import org.openstreetmap.josm.actions.MergeNodesAction;
import org.openstreetmap.josm.command.Command;
import org.openstreetmap.josm.command.DeleteCommand;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.osm.AbstractPrimitive;
import org.openstreetmap.josm.data.osm.BBox;
import org.openstreetmap.josm.data.osm.DataSet;
@ -124,8 +125,8 @@ public class GetDataRunnable extends RecursiveTask<DataSet> implements CancelLis
}
// This can technically be included in the above block, but it is here so that
// cancellation is a little faster
if (!monitor.isCanceled()) {
cleanup(dataSet);
if (!monitor.isCanceled() && !bboxes.isEmpty()) {
cleanup(dataSet, new Bounds(bboxes.get(0).getBottomRight(), bboxes.get(0).getTopLeft()));
}
monitor.finishTask();
return dataSet;
@ -135,11 +136,14 @@ public class GetDataRunnable extends RecursiveTask<DataSet> implements CancelLis
* Perform cleanups on a dataset (one dataset at a time)
*
* @param dataSet The dataset to cleanup
* @param bounds
*/
public static void cleanup(DataSet dataSet) {
public static void cleanup(DataSet dataSet, Bounds bounds) {
synchronized (LOCK) {
/* Microsoft buildings don't have a source, so we add one */
MapWithAIDataUtils.addSourceTags(dataSet, "building", "microsoft/BuildingFootprints");
// MapWithAIDataUtils.addSourceTags(dataSet, "building",
// "microsoft/BuildingFootprints");
removeRedundantSource(dataSet);
replaceTags(dataSet);
removeCommonTags(dataSet);
mergeNodes(dataSet);
@ -147,25 +151,48 @@ public class GetDataRunnable extends RecursiveTask<DataSet> implements CancelLis
mergeWays(dataSet);
removeAlreadyAddedData(dataSet);
new MergeDuplicateWays(dataSet).executeCommand();
dataSet.getWays().parallelStream().filter(way -> !way.isDeleted())
.forEach(GetDataRunnable::cleanupArtifacts);
(bounds == null ? dataSet.getWays() : dataSet.searchWays(bounds.toBBox())).parallelStream()
.filter(way -> !way.isDeleted())
.forEach(GetDataRunnable::cleanupArtifacts);
}
}
/**
* Remove redudant sources from objects (if source on way and source on node,
* and node doesn't have any other tags, then node doesn't need the source)
*
* @param dataSet The dataset with potential duplicate source tags
*/
public static void removeRedundantSource(DataSet dataSet) {
dataSet.getNodes().parallelStream().filter(node -> !node.getReferrers().isEmpty())
.filter(node -> node.getKeys().entrySet().parallelStream().map(Entry::getKey)
.allMatch(key -> key.contains("source"))
&& node.getKeys().entrySet().parallelStream()
.allMatch(entry -> node.getReferrers().parallelStream()
.anyMatch(parent -> parent.hasTag(entry.getKey(), entry.getValue()))))
.forEach(node -> node.getKeys().entrySet().parallelStream().map(Entry::getKey)
.filter(key -> key.contains("source")).forEach(node::remove));
}
/**
* Remove ways that have already been added to an OSM layer
*
* @param dataSet The dataset with potential duplicate ways (it is modified)
*/
public static void removeAlreadyAddedData(DataSet dataSet) {
final List<DataSet> osmData = MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class)
.parallelStream().map(OsmDataLayer::getDataSet).filter(ds -> !ds.equals(dataSet))
.collect(Collectors.toList());
dataSet.getWays().parallelStream().filter(way -> !way.isDeleted())
.filter(way -> osmData.stream().anyMatch(ds -> checkIfPrimitiveDuplicatesPrimitiveInDataSet(way, ds)))
.forEach(way -> {
final List<Node> nodes = way.getNodes();
DeleteCommand.delete(Collections.singleton(way), true, true).executeCommand();
nodes.parallelStream()
.filter(node -> !node.isDeleted()
&& node.getReferrers().parallelStream().allMatch(OsmPrimitive::isDeleted))
.forEach(node -> node.setDeleted(true));
});
.filter(way -> osmData.stream().anyMatch(ds -> checkIfPrimitiveDuplicatesPrimitiveInDataSet(way, ds)))
.forEach(way -> {
final List<Node> nodes = way.getNodes();
DeleteCommand.delete(Collections.singleton(way), true, true).executeCommand();
nodes.parallelStream()
.filter(node -> !node.isDeleted()
&& node.getReferrers().parallelStream().allMatch(OsmPrimitive::isDeleted))
.forEach(node -> node.setDeleted(true));
});
}
private static boolean checkIfPrimitiveDuplicatesPrimitiveInDataSet(OsmPrimitive primitive, DataSet ds) {
@ -251,7 +278,7 @@ public class GetDataRunnable extends RecursiveTask<DataSet> implements CancelLis
*/
public static void removeCommonTags(DataSet dataSet) {
dataSet.allPrimitives().parallelStream().filter(prim -> prim.hasKey(MergeDuplicateWays.ORIG_ID))
.forEach(prim -> prim.remove(MergeDuplicateWays.ORIG_ID));
.forEach(prim -> prim.remove(MergeDuplicateWays.ORIG_ID));
dataSet.getNodes().parallelStream().forEach(node -> node.remove(SERVER_ID_KEY));
final List<Node> emptyNodes = dataSet.getNodes().parallelStream().distinct().filter(node -> !node.isDeleted())
.filter(node -> node.getReferrers().isEmpty() && !node.hasKeys()).collect(Collectors.toList());
@ -296,7 +323,7 @@ public class GetDataRunnable extends RecursiveTask<DataSet> implements CancelLis
.collect(Collectors.toList());
way1.getNodePairs(false);
nearbyWays.parallelStream().flatMap(way2 -> checkWayDuplications(way1, way2).entrySet().parallelStream())
.forEach(GetDataRunnable::addMissingElement);
.forEach(GetDataRunnable::addMissingElement);
}
}
@ -399,7 +426,7 @@ public class GetDataRunnable extends RecursiveTask<DataSet> implements CancelLis
private static DataSet getDataReal(BBox bbox, ProgressMonitor monitor) {
final DataSet dataSet = new DataSet();
final List<Map<String, String>> urlMaps = MapWithAIPreferenceHelper.getMapWithAIUrl().stream()
.map(map -> new TreeMap<>(map)).collect(Collectors.toList());
.map(TreeMap::new).collect(Collectors.toList());
if (DetectTaskingManagerUtils.hasTaskingManagerLayer()) {
urlMaps.forEach(map -> map.put("url", map.get("url").concat("&crop_bbox={crop_bbox}")));
}
@ -424,6 +451,14 @@ public class GetDataRunnable extends RecursiveTask<DataSet> implements CancelLis
return dataSet;
}
/**
* Add information to the user agent and then perform the actual internet call
*
* @param client The HttpClient
* @param dataSet The dataset to add data to
* @param source The source of the data (added as a tag to "whole" objects)
* @param monitor The monitor (so we know when a cancellation has occurred)
*/
private static void clientCall(HttpClient client, DataSet dataSet, String source, ProgressMonitor monitor) {
final StringBuilder defaultUserAgent = new StringBuilder();
client.setReadTimeout(DEFAULT_TIMEOUT);
@ -434,23 +469,49 @@ public class GetDataRunnable extends RecursiveTask<DataSet> implements CancelLis
defaultUserAgent.append(tr("/ {0} {1}", MapWithAIPlugin.NAME, MapWithAIPlugin.getVersionInfo()));
client.setHeader("User-Agent", defaultUserAgent.toString());
if (!monitor.isCanceled()) {
InputStream inputStream = null;
try {
Logging.debug("{0}: Getting {1}", MapWithAIPlugin.NAME, client.getURL().toString());
final Response response = client.connect();
inputStream = response.getContent();
final DataSet mergeData = OsmReaderCustom.parseDataSet(inputStream, null, true);
addMapWithAISourceTag(mergeData, source);
dataSet.mergeFrom(mergeData);
response.disconnect();
} catch (final SocketException e) {
if (!monitor.isCanceled()) {
clientCallInternet(client, dataSet, source, monitor);
}
}
/**
* Add perform an internet request to add data to a dataset
*
* @param client The HttpClient
* @param dataSet The dataset to add data to
* @param source The source of the data (added as a tag to "whole" objects)
* @param monitor The monitor (so we know when a cancellation has occurred)
*/
private static void clientCallInternet(HttpClient client, DataSet dataSet, String source, ProgressMonitor monitor) {
InputStream inputStream = null;
try {
Logging.debug("{0}: Getting {1}", MapWithAIPlugin.NAME, client.getURL().toString());
final Response response = client.connect();
inputStream = response.getContent();
final DataSet mergeData = OsmReaderCustom.parseDataSet(inputStream, null, true);
addMapWithAISourceTag(mergeData, source);
dataSet.mergeFrom(mergeData);
response.disconnect();
} catch (final SocketException e) {
if (!monitor.isCanceled()) {
Logging.debug(e);
}
} catch (final SSLException e) {
Logging.debug(e);
new Notification(tr("{0}: Bad SSL Certificate: {1}", MapWithAIPlugin.NAME, client.getURL()))
.setDuration(Notification.TIME_DEFAULT).show();
} catch (UnsupportedOperationException | IllegalDataException | IOException e) {
Logging.debug(e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (final IOException e) {
Logging.debug(e);
}
} catch (final SSLException e) {
Logging.debug(e);
new Notification(tr("{0}: Bad SSL Certificate: {1}", MapWithAIPlugin.NAME, client.getURL()))
.setDuration(Notification.TIME_DEFAULT).show();
.setDuration(Notification.TIME_DEFAULT).show();
} catch (UnsupportedOperationException | IllegalDataException | IOException e) {
Logging.debug(e);
} finally {
@ -470,13 +531,13 @@ public class GetDataRunnable extends RecursiveTask<DataSet> implements CancelLis
* @param source The source to associate with the data
* @return The dataset for easy chaining
*/
protected static DataSet addMapWithAISourceTag(DataSet dataSet, String source) {
public static DataSet addMapWithAISourceTag(DataSet dataSet, String source) {
dataSet.getNodes().parallelStream().filter(node -> !node.isDeleted() && node.getReferrers().isEmpty())
.forEach(node -> node.put(MAPWITHAI_SOURCE_TAG_KEY, source));
.forEach(node -> node.put(MAPWITHAI_SOURCE_TAG_KEY, source));
dataSet.getWays().parallelStream().filter(way -> !way.isDeleted())
.forEach(way -> way.put(MAPWITHAI_SOURCE_TAG_KEY, source));
.forEach(way -> way.put(MAPWITHAI_SOURCE_TAG_KEY, source));
dataSet.getRelations().parallelStream().filter(rel -> !rel.isDeleted())
.forEach(rel -> rel.put(MAPWITHAI_SOURCE_TAG_KEY, source));
.forEach(rel -> rel.put(MAPWITHAI_SOURCE_TAG_KEY, source));
return dataSet;
}

Wyświetl plik

@ -1,148 +0,0 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.backend;
import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.Optional;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.BBox;
import org.openstreetmap.josm.gui.ExtendedDialog;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.MapView;
import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
import org.openstreetmap.josm.gui.layer.GpxLayer;
import org.openstreetmap.josm.gui.widgets.JosmTextField;
import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
import org.openstreetmap.josm.spi.preferences.Config;
import org.openstreetmap.josm.tools.GBC;
import org.openstreetmap.josm.tools.OsmUrlToBounds;
import org.openstreetmap.josm.tools.Shortcut;
/**
* @author Taylor Smock
*/
public class MapWithAIArbitraryAction extends JosmAction {
private static final long serialVersionUID = 9048113038651190619L;
private final transient JosmTextField lowerLat = new JosmTextField();
private final transient JosmTextField upperLat = new JosmTextField();
private final transient JosmTextField leftLon = new JosmTextField();
private final transient JosmTextField rightLon = new JosmTextField();
private final transient JCheckBox checkbox = new JCheckBox();
private static final String ARBITRARY_DATA_STRING = tr("Get arbitrary data from {0}", MapWithAIPlugin.NAME);
public MapWithAIArbitraryAction() {
super(tr("{0}: Download arbitrary data", MapWithAIPlugin.NAME), "mapwithai", tr(ARBITRARY_DATA_STRING),
Shortcut.registerShortcut("data:arbitrarymapwithai",
tr("Data: Arbitrary {0} Data", tr(ARBITRARY_DATA_STRING)), KeyEvent.VK_R,
Shortcut.ALT_CTRL_SHIFT),
true);
setHelpId(ht("Plugin/MapWithAI#BasicUsage"));
}
@Override
public void actionPerformed(ActionEvent e) {
showDownloadDialog();
}
static class MapWithAIArbitraryDialog extends ExtendedDialog {
private static final long serialVersionUID = 2795301151521238635L;
MapWithAIArbitraryDialog(String[] buttons, JPanel panel) {
super(MainApplication.getMainFrame(), tr(ARBITRARY_DATA_STRING), buttons);
setButtonIcons("ok", "cancel");
configureContextsensitiveHelp(ht("Plugin/MapWithAI#BasicUsage"), true);
setContent(panel);
setCancelButton(2);
}
}
public void showDownloadDialog() {
final Optional<Bounds> boundsFromClipboard = Optional.ofNullable(ClipboardUtils.getClipboardStringContent())
.map(OsmUrlToBounds::parse);
if (boundsFromClipboard.isPresent() && Config.getPref().getBoolean("jumpto.use.clipboard", true)) {
setBounds(boundsFromClipboard.get());
} else if (MainApplication.isDisplayingMapView()) {
final MapView mv = MainApplication.getMap().mapView;
setBounds(mv.getState().getViewArea().getCornerBounds());
}
final JPanel panel = new JPanel(new BorderLayout());
panel.add(new JLabel(
"<html>" + tr("Enter Lat/Lon to download {0} data.", MapWithAIPlugin.NAME) + "<br>" + "</html>"),
BorderLayout.NORTH);
SelectAllOnFocusGainedDecorator.decorate(lowerLat);
SelectAllOnFocusGainedDecorator.decorate(leftLon);
final JPanel p = new JPanel(new GridBagLayout());
panel.add(p, BorderLayout.NORTH);
p.add(new JLabel(tr("Lower Latitude")), GBC.eol());
p.add(lowerLat, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
p.add(new JLabel(tr("Left Longitude")), GBC.eol());
p.add(leftLon, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
p.add(new JLabel(tr("Upper Latitude")), GBC.eol());
p.add(upperLat, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
p.add(new JLabel(tr("Right Longitude")), GBC.eol());
p.add(rightLon, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
p.add(new JLabel(tr("Crop to download area?")));
p.add(checkbox, GBC.eol());
final String[] buttons = { tr("Download"), tr("Cancel") };
BBox bbox = new BBox();
while (!bbox.isInWorld()) {
final int option = new MapWithAIArbitraryDialog(buttons, panel).showDialog().getValue();
if (option != 1) {
return;
}
try {
bbox = new BBox();
bbox.add(new LatLon(Double.parseDouble(lowerLat.getText()), Double.parseDouble(leftLon.getText())));
bbox.add(new LatLon(Double.parseDouble(upperLat.getText()), Double.parseDouble(rightLon.getText())));
} catch (final NumberFormatException e) {
JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
tr("Could not parse Latitude or Longitude. Please check."), tr("Unable to parse Lon/Lat"),
JOptionPane.ERROR_MESSAGE);
bbox = new BBox();
}
}
if (checkbox.isSelected()) {
MainApplication.getLayerManager()
.addLayer(new GpxLayer(DetectTaskingManagerUtils.createTaskingManagerGpxData(bbox),
DetectTaskingManagerUtils.MAPWITHAI_CROP_AREA));
}
MapWithAIDataUtils.getMapWithAIData(MapWithAIDataUtils.getLayer(true), bbox);
}
private void setBounds(Bounds b) {
if (b != null) {
final LatLon min = b.getMin();
final LatLon max = b.getMax();
lowerLat.setText(Double.toString(min.lat()));
leftLon.setText(Double.toString(min.lon()));
rightLon.setText(Double.toString(max.lon()));
upperLat.setText(Double.toString(max.lat()));
}
}
}

Wyświetl plik

@ -45,6 +45,7 @@ import org.openstreetmap.josm.tools.Utils;
*
*/
public final class MapWithAIDataUtils {
/** THe maximum dimensions for MapWithAI data (in kilometers) */
public static final int MAXIMUM_SIDE_DIMENSIONS = 10_000; // RapiD is about 1km, max is 10km
private static final int TOO_MANY_BBOXES = 4;
private static ForkJoinPool forkJoinPool;
@ -69,7 +70,7 @@ public final class MapWithAIDataUtils {
"https://gitlab.com/smocktaylor/rapid/raw/master/src/resources/styles/standard/rapid.mapcss",
"resource://styles/standard/mapwithai.mapcss");
new ArrayList<>(MapPaintStyles.getStyles().getStyleSources()).parallelStream()
.filter(style -> oldUrls.contains(style.url)).forEach(MapPaintStyles::removeStyle);
.filter(style -> oldUrls.contains(style.url)).forEach(MapPaintStyles::removeStyle);
if (!checkIfMapWithAIPaintStyleExists()) {
final MapCSSStyleSource style = new MapCSSStyleSource(paintStyleResourceUrl, MapWithAIPlugin.NAME,
@ -172,15 +173,15 @@ public final class MapWithAIDataUtils {
} else {
final Notification noUrls = MapWithAIPreferenceHelper.getMapWithAIURLs().isEmpty()
? new Notification(tr("There are no defined URLs. To get the defaults, restart JOSM"))
: new Notification(tr("No URLS are enabled"));
noUrls.setDuration(Notification.TIME_DEFAULT);
noUrls.setIcon(JOptionPane.INFORMATION_MESSAGE);
noUrls.setHelpTopic(ht("Plugin/MapWithAI#Preferences"));
if (SwingUtilities.isEventDispatchThread()) {
noUrls.show();
} else {
SwingUtilities.invokeLater(noUrls::show);
}
: new Notification(tr("No URLS are enabled"));
noUrls.setDuration(Notification.TIME_DEFAULT);
noUrls.setIcon(JOptionPane.INFORMATION_MESSAGE);
noUrls.setHelpTopic(ht("Plugin/MapWithAI#Preferences"));
if (SwingUtilities.isEventDispatchThread()) {
noUrls.show();
} else {
SwingUtilities.invokeLater(noUrls::show);
}
}
return dataSet;
}
@ -291,7 +292,7 @@ public final class MapWithAIDataUtils {
lock.lock();
try {
mapWithAISet.mergeFrom(newData);
GetDataRunnable.cleanup(mapWithAISet);
GetDataRunnable.cleanup(mapWithAISet, null);
} finally {
lock.unlock();
}

Wyświetl plik

@ -16,6 +16,7 @@ import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.DownloadPolicy;
import org.openstreetmap.josm.data.osm.UploadPolicy;
@ -176,4 +177,14 @@ public class MapWithAILayer extends OsmDataLayer implements ActiveLayerChangeLis
return ImageProvider.getIfAvailable("mapwithai") == null ? super.getIcon()
: ImageProvider.get("mapwithai", ImageProvider.ImageSizes.LAYER);
}
/**
* Call after download from server
*
* @param bounds The newly added bounds
*/
public void onPostDownloadFromServer(Bounds bounds) {
super.onPostDownloadFromServer();
GetDataRunnable.cleanup(getDataSet(), bounds);
}
}

Wyświetl plik

@ -47,14 +47,15 @@ public final class MapWithAIPreferenceHelper {
/**
* Get the current MapWithAI urls
*
* @return A list of enabled MapWithAI urls
* @return A list of enabled MapWithAI urls (maps have source, parameters,
* enabled, and the url)
*/
public static List<Map<String, String>> getMapWithAIUrl() {
final MapWithAILayer layer = MapWithAIDataUtils.getLayer(false);
return (layer != null) && (layer.getMapWithAIUrl() != null)
? getMapWithAIURLs().parallelStream().filter(map -> layer.getMapWithAIUrl().equals(map.get(URL_STRING)))
.collect(Collectors.toList())
: getMapWithAIURLs().stream()
: getMapWithAIURLs().stream()
.filter(map -> Boolean.valueOf(map.getOrDefault(ENABLED_STRING, Boolean.FALSE.toString())))
.collect(Collectors.toList());
}
@ -62,11 +63,12 @@ public final class MapWithAIPreferenceHelper {
/**
* Get all of the MapWithAI urls (or the default)
*
* @return The urls for MapWithAI endpoints
* @return The urls for MapWithAI endpoints (maps have source, parameters,
* enabled, and the url)
*/
public static List<Map<String, String>> getMapWithAIURLs() {
final List<Map<String, String>> returnMap = Config.getPref().getListOfMaps(API_MAP_CONFIG, new ArrayList<>())
.stream().map(map -> new TreeMap<>(map)).collect(Collectors.toList());
.stream().map(TreeMap::new).collect(Collectors.toList());
if (returnMap.isEmpty()) {
final List<String> defaultAPIs = Collections.singletonList(DEFAULT_MAPWITHAI_API);
final List<String> defaultList = Config.getPref().getList(API_CONFIG).isEmpty() ? defaultAPIs

Wyświetl plik

@ -20,6 +20,7 @@ import org.openstreetmap.josm.command.ChangeCommand;
import org.openstreetmap.josm.command.Command;
import org.openstreetmap.josm.command.DeleteCommand;
import org.openstreetmap.josm.command.SequenceCommand;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
@ -37,6 +38,8 @@ public class MergeDuplicateWays extends Command {
private final List<Command> commands;
private Command command;
private Bounds bound;
public MergeDuplicateWays(DataSet data) {
this(data, null, null);
}
@ -64,7 +67,7 @@ public class MergeDuplicateWays extends Command {
public boolean executeCommand() {
if (commands.isEmpty() || (command == null)) {
if ((way1 == null) && (way2 == null)) {
filterDataSet(getAffectedDataSet(), commands);
filterDataSet(getAffectedDataSet(), commands, bound);
} else if ((way1 != null) && (way2 == null)) {
checkForDuplicateWays(way1, commands);
} else if (way1 == null) {
@ -98,8 +101,18 @@ public class MergeDuplicateWays extends Command {
}
}
public static void filterDataSet(DataSet dataSet, List<Command> commands) {
final List<Way> ways = new ArrayList<>(dataSet.getWays().parallelStream()
/**
* Set the bounds for the command. Must be called before execution.
*
* @param bound The boundary for the command
*/
public void setBounds(Bounds bound) {
this.bound = bound;
}
public static void filterDataSet(DataSet dataSet, List<Command> commands, Bounds bound) {
final List<Way> ways = new ArrayList<>(
(bound == null ? dataSet.getWays() : dataSet.searchWays(bound.toBBox())).parallelStream()
.filter(prim -> !prim.isIncomplete() && !prim.isDeleted()).collect(Collectors.toList()));
for (int i = 0; i < ways.size(); i++) {
final Way way1 = ways.get(i);

Wyświetl plik

@ -0,0 +1,194 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.frontend;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.json.JsonObject;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import org.openstreetmap.josm.actions.downloadtasks.DownloadParams;
import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.download.AbstractDownloadSourcePanel;
import org.openstreetmap.josm.gui.download.DownloadDialog;
import org.openstreetmap.josm.gui.download.DownloadSettings;
import org.openstreetmap.josm.gui.download.DownloadSource;
import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
import org.openstreetmap.josm.plugins.mapwithai.backend.BoundingBoxMapWithAIDownloader;
import org.openstreetmap.josm.plugins.mapwithai.backend.DetectTaskingManagerUtils;
import org.openstreetmap.josm.plugins.mapwithai.backend.DownloadMapWithAITask;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIDataUtils;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIPreferenceHelper;
import org.openstreetmap.josm.plugins.mapwithai.backend.commands.conflation.DataUrl;
import org.openstreetmap.josm.tools.GBC;
import org.openstreetmap.josm.tools.ImageProvider;
public class MapWithAIDownloadReader implements DownloadSource<MapWithAIDownloadReader.MapWithAIDownloadData> {
@Override
public AbstractDownloadSourcePanel<MapWithAIDownloadData> createPanel(DownloadDialog dialog) {
return new MapWithAIDownloadPanel(this);
}
@Override
public void doDownload(MapWithAIDownloadData data, DownloadSettings settings) {
Bounds area = settings.getDownloadBounds().orElse(new Bounds(0, 0, 0, 0));
DownloadMapWithAITask task = new DownloadMapWithAITask();
task.setZoomAfterDownload(settings.zoomToData());
data.getUrls().forEach(url -> {
Future<?> future = task.download(new BoundingBoxMapWithAIDownloader(area, getUrl(url),
DetectTaskingManagerUtils.hasTaskingManagerLayer()), new DownloadParams(), area, null);
MainApplication.worker.execute(new PostDownloadHandler(task, future, data.getErrorReporter()));
});
}
private String getUrl(Map<String, String> urlInformation) {
StringBuilder sb = new StringBuilder();
if (urlInformation.containsKey("url")) {
sb.append(urlInformation.get("url"));
if (urlInformation.containsKey("parameters")) {
List<String> parameters = DataUrl.readJsonStringArraySimple(urlInformation.get("parameters"))
.parallelStream()
.filter(JsonObject.class::isInstance).map(JsonObject.class::cast)
.filter(map -> map.getBoolean("enabled", false)).filter(map -> map.containsKey("parameter"))
.map(map -> map.getString("parameter")).collect(Collectors.toList());
if (!parameters.isEmpty()) {
sb.append("&").append(String.join("&", parameters));
}
}
}
return sb.toString();
}
@Override
public String getLabel() {
return tr("Download from {0} API", MapWithAIPlugin.NAME);
}
@Override
public boolean onlyExpert() {
return false;
}
/**
* Encapsulates data that is required to perform download from MapWithAI API
*/
static class MapWithAIDownloadData {
private final List<Map<String, String>> url;
private final Consumer<Collection<Object>> errorReporter;
MapWithAIDownloadData(List<Map<String, String>> list, Consumer<Collection<Object>> errorReporter) {
this.url = list;
this.errorReporter = errorReporter;
}
List<Map<String, String>> getUrls() {
return url;
}
Consumer<Collection<Object>> getErrorReporter() {
return errorReporter;
}
}
public static class MapWithAIDownloadPanel extends AbstractDownloadSourcePanel<MapWithAIDownloadData> {
private static final long serialVersionUID = -6934457612643307520L;
private final JLabel sizeCheck = new JLabel();
public MapWithAIDownloadPanel(MapWithAIDownloadReader downloadReader) {
super(downloadReader);
setLayout(new GridBagLayout());
Font labelFont = sizeCheck.getFont();
sizeCheck.setFont(labelFont.deriveFont(Font.PLAIN, labelFont.getSize()));
add(sizeCheck, GBC.eol().anchor(GridBagConstraints.EAST).insets(5, 5, 5, 2));
setMinimumSize(new Dimension(450, 115));
}
@Override
public MapWithAIDownloadData getData() {
Consumer<Collection<Object>> errorReporter = errors -> {
boolean onlyNoDataError = errors.size() == 1 && errors.contains("No data found in this area.");
};
return new MapWithAIDownloadData(MapWithAIPreferenceHelper.getMapWithAIUrl(), errorReporter);
}
@Override
public void rememberSettings() {
// Do nothing
}
@Override
public void restoreSettings() {
// Do nothing
}
@Override
public boolean checkDownload(DownloadSettings settings) {
if (!settings.getDownloadBounds().isPresent()) {
JOptionPane.showMessageDialog(this.getParent(), tr("Please select a download area first."), tr("Error"),
JOptionPane.ERROR_MESSAGE);
}
return settings.getDownloadBounds().isPresent();
}
@Override
public String getSimpleName() {
return "mapwithaidownloadpanel";
}
@Override
public Icon getIcon() {
return new ImageProvider("dialogs", "mapwithai")
.setMaxHeight(ImageProvider.ImageSizes.SIDEBUTTON.getVirtualHeight()).get();
}
@Override
public void boundingBoxChanged(Bounds bbox) {
updateSizeCheck(bbox);
}
private void updateSizeCheck(Bounds bbox) {
if (bbox == null) {
sizeCheck.setText(tr("No area selected yet"));
sizeCheck.setForeground(Color.darkGray);
return;
}
double width = Math.max(bbox.getMin().greatCircleDistance(new LatLon(bbox.getMinLat(), bbox.getMaxLon())),
bbox.getMax().greatCircleDistance(new LatLon(bbox.getMaxLat(), bbox.getMinLon())));
double height = Math.max(bbox.getMin().greatCircleDistance(new LatLon(bbox.getMaxLat(), bbox.getMinLon())),
bbox.getMax().greatCircleDistance(new LatLon(bbox.getMinLat(), bbox.getMaxLon())));
displaySizeCheckResult(height > MapWithAIDataUtils.MAXIMUM_SIDE_DIMENSIONS
|| width > MapWithAIDataUtils.MAXIMUM_SIDE_DIMENSIONS);
}
private void displaySizeCheckResult(boolean isAreaTooLarge) {
if (isAreaTooLarge) {
sizeCheck.setText(tr("Download area too large; will probably be rejected by server"));
sizeCheck.setForeground(Color.red);
} else {
sizeCheck.setText(tr("Download area ok, size probably acceptable to server"));
sizeCheck.setForeground(Color.darkGray);
}
}
}
}

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -75,11 +75,11 @@ public class MapWithAIPluginTest {
*/
@Test
public void testMapWithAIPlugin() {
final int addedMenuItems = 3;
final JMenu dataMenu = MainApplication.getMenu().dataMenu;
final int dataMenuSize = dataMenu.getMenuComponentCount();
plugin = new MapWithAIPlugin(info);
Assert.assertEquals(dataMenuSize + 4, dataMenu.getMenuComponentCount());
MapPaintStyles.getStyles().getStyleSources().forEach(style -> Logging.error(style.toString()));
Assert.assertEquals(dataMenuSize + addedMenuItems, dataMenu.getMenuComponentCount());
Assert.assertEquals(1, MapPaintStyles.getStyles().getStyleSources().parallelStream()
.filter(source -> source.url != null && source.name.contains("MapWithAI")).count());
@ -89,15 +89,15 @@ public class MapWithAIPluginTest {
plugin.destroy();
Assert.assertEquals(dataMenuSize, dataMenu.getMenuComponentCount());
Awaitility.await().atMost(Durations.FIVE_SECONDS)
.until(() -> existed == MapWithAIDataUtils.checkIfMapWithAIPaintStyleExists());
.until(() -> existed == MapWithAIDataUtils.checkIfMapWithAIPaintStyleExists());
Assert.assertEquals(Config.getPref().getBoolean(MapWithAIPlugin.PAINTSTYLE_PREEXISTS) ? 1 : 0,
MapPaintStyles.getStyles().getStyleSources().parallelStream()
.filter(source -> source.url != null && source.name.contains("MapWithAI")).count());
.filter(source -> source.url != null && source.name.contains("MapWithAI")).count());
}
for (int i = 0; i < 3; i++) {
plugin = new MapWithAIPlugin(info);
Assert.assertEquals(dataMenuSize + 4, dataMenu.getMenuComponentCount());
Assert.assertEquals(dataMenuSize + addedMenuItems, dataMenu.getMenuComponentCount());
Assert.assertEquals(1, MapPaintStyles.getStyles().getStyleSources().parallelStream()
.filter(source -> source.url != null && source.name.contains("MapWithAI")).count());
}

Wyświetl plik

@ -0,0 +1,84 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.frontend;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.util.stream.Collectors;
import org.awaitility.Awaitility;
import org.awaitility.Durations;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.gui.download.DownloadSettings;
import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIDataUtils;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIPreferenceHelper;
import org.openstreetmap.josm.testutils.JOSMTestRules;
import com.github.tomakehurst.wiremock.WireMockServer;
public class MapWithAIDownloadReaderTest {
@Rule
public JOSMTestRules rules = new JOSMTestRules().projection();
WireMockServer wireMock = new WireMockServer(options().usingFilesUnderDirectory("test/resources/wiremock"));
@Before
public void setUp() {
wireMock.start();
MapWithAIPreferenceHelper.setMapWithAIURLs(MapWithAIPreferenceHelper.getMapWithAIURLs().stream().map(map -> {
map.put("url", getDefaultMapWithAIAPIForTest(
map.getOrDefault("url", MapWithAIPreferenceHelper.DEFAULT_MAPWITHAI_API)));
return map;
}).collect(Collectors.toList()));
}
@After
public void tearDown() {
wireMock.stop();
}
private String getDefaultMapWithAIAPIForTest(String url) {
return getDefaultMapWithAIAPIForTest(url, "https://www.facebook.com");
}
private String getDefaultMapWithAIAPIForTest(String url, String wireMockReplace) {
return url.replace(wireMockReplace, wireMock.baseUrl());
}
@Test
public void testGetLabel() {
assertEquals(tr("Download from {0} API", MapWithAIPlugin.NAME), new MapWithAIDownloadReader().getLabel());
}
@Test
public void testIsExpert() {
assertFalse(new MapWithAIDownloadReader().onlyExpert());
}
@Test
public void testDoDownload() {
MapWithAIDownloadReader reader = new MapWithAIDownloadReader();
DownloadSettings settings = new DownloadSettings(
new Bounds(39.0239848, -108.4522247, 39.1066201, -108.3368683), false, false);
MapWithAIDownloadReader.MapWithAIDownloadData data = new MapWithAIDownloadReader.MapWithAIDownloadData(
MapWithAIPreferenceHelper.getMapWithAIUrl(), errors -> {
});
reader.doDownload(data, settings);
Awaitility.await().atMost(Durations.TEN_SECONDS).until(() -> MapWithAIDataUtils.getLayer(false) != null);
assertNotNull(MapWithAIDataUtils.getLayer(false));
Awaitility.await().atMost(Durations.TEN_SECONDS)
.until(() -> !MapWithAIDataUtils.getLayer(false).getDataSet().getDataSourceBounds().isEmpty());
assertFalse(MapWithAIDataUtils.getLayer(false).getDataSet().getDataSourceBounds().isEmpty());
assertTrue(settings.getDownloadBounds().get().toBBox().bboxIsFunctionallyEqual(
MapWithAIDataUtils.getLayer(false).getDataSet().getDataSourceBounds().get(0).toBBox(), 0.0001));
}
}