kopia lustrzana https://github.com/JOSM/MapWithAI
MapWithAILayerInfo: Don't block JOSM startup
Signed-off-by: Taylor Smock <tsmock@fb.com>pull/1/head
rodzic
64ff82db04
commit
ed6b8bcd5d
|
@ -24,6 +24,7 @@ import org.openstreetmap.josm.gui.MapFrame;
|
||||||
import org.openstreetmap.josm.gui.download.DownloadDialog;
|
import org.openstreetmap.josm.gui.download.DownloadDialog;
|
||||||
import org.openstreetmap.josm.gui.download.OSMDownloadSource;
|
import org.openstreetmap.josm.gui.download.OSMDownloadSource;
|
||||||
import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
|
import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
|
||||||
|
import org.openstreetmap.josm.gui.util.GuiHelper;
|
||||||
import org.openstreetmap.josm.io.remotecontrol.RequestProcessor;
|
import org.openstreetmap.josm.io.remotecontrol.RequestProcessor;
|
||||||
import org.openstreetmap.josm.plugins.Plugin;
|
import org.openstreetmap.josm.plugins.Plugin;
|
||||||
import org.openstreetmap.josm.plugins.PluginInformation;
|
import org.openstreetmap.josm.plugins.PluginInformation;
|
||||||
|
@ -35,6 +36,7 @@ import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIObject;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIRemoteControl;
|
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIRemoteControl;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIUploadHook;
|
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIUploadHook;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.backend.MergeDuplicateWaysAction;
|
import org.openstreetmap.josm.plugins.mapwithai.backend.MergeDuplicateWaysAction;
|
||||||
|
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.PreConflatedDataUtils;
|
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.PreConflatedDataUtils;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.ConnectingNodeInformationTest;
|
import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.ConnectingNodeInformationTest;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.RoutingIslandsTest;
|
import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.RoutingIslandsTest;
|
||||||
|
@ -118,6 +120,9 @@ public final class MapWithAIPlugin extends Plugin implements Destroyable {
|
||||||
MapPaintUtils.addMapWithAIPaintStyles();
|
MapPaintUtils.addMapWithAIPaintStyles();
|
||||||
|
|
||||||
destroyables = new ArrayList<>();
|
destroyables = new ArrayList<>();
|
||||||
|
// Run in EDT to avoid blocking (has to be run before MapWithAIDownloadOptions
|
||||||
|
// so its already initialized)
|
||||||
|
GuiHelper.runInEDT(MapWithAILayerInfo::getInstance);
|
||||||
MapWithAIDownloadOptions mapWithAIDownloadOptions = new MapWithAIDownloadOptions();
|
MapWithAIDownloadOptions mapWithAIDownloadOptions = new MapWithAIDownloadOptions();
|
||||||
mapWithAIDownloadOptions.addGui(DownloadDialog.getInstance());
|
mapWithAIDownloadOptions.addGui(DownloadDialog.getInstance());
|
||||||
destroyables.add(mapWithAIDownloadOptions);
|
destroyables.add(mapWithAIDownloadOptions);
|
||||||
|
|
|
@ -16,18 +16,18 @@ import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.ForkJoinPool;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
import org.openstreetmap.josm.actions.ExpertToggleAction;
|
import org.openstreetmap.josm.actions.ExpertToggleAction;
|
||||||
import org.openstreetmap.josm.data.StructUtils;
|
import org.openstreetmap.josm.data.StructUtils;
|
||||||
import org.openstreetmap.josm.data.imagery.ImageryInfo;
|
import org.openstreetmap.josm.data.imagery.ImageryInfo;
|
||||||
import org.openstreetmap.josm.data.preferences.BooleanProperty;
|
import org.openstreetmap.josm.data.preferences.BooleanProperty;
|
||||||
import org.openstreetmap.josm.gui.MainApplication;
|
|
||||||
import org.openstreetmap.josm.gui.PleaseWaitRunnable;
|
import org.openstreetmap.josm.gui.PleaseWaitRunnable;
|
||||||
import org.openstreetmap.josm.gui.util.GuiHelper;
|
|
||||||
import org.openstreetmap.josm.io.CachedFile;
|
import org.openstreetmap.josm.io.CachedFile;
|
||||||
import org.openstreetmap.josm.io.NetworkManager;
|
import org.openstreetmap.josm.io.NetworkManager;
|
||||||
import org.openstreetmap.josm.io.imagery.ImageryReader;
|
import org.openstreetmap.josm.io.imagery.ImageryReader;
|
||||||
|
@ -48,6 +48,8 @@ public class MapWithAILayerInfo {
|
||||||
*/
|
*/
|
||||||
public static final BooleanProperty SHOW_PREVIEW = new BooleanProperty("mapwithai.sources.preview", false);
|
public static final BooleanProperty SHOW_PREVIEW = new BooleanProperty("mapwithai.sources.preview", false);
|
||||||
|
|
||||||
|
/** Finish listeners */
|
||||||
|
private ListenerList<FinishListener> finishListenerListenerList = ListenerList.create();
|
||||||
/** List of all usable layers */
|
/** List of all usable layers */
|
||||||
private final List<MapWithAIInfo> layers = Collections.synchronizedList(new ArrayList<>());
|
private final List<MapWithAIInfo> layers = Collections.synchronizedList(new ArrayList<>());
|
||||||
/** List of layer ids of all usable layers */
|
/** List of layer ids of all usable layers */
|
||||||
|
@ -70,23 +72,29 @@ public class MapWithAILayerInfo {
|
||||||
private static MapWithAILayerInfo instance;
|
private static MapWithAILayerInfo instance;
|
||||||
|
|
||||||
public static MapWithAILayerInfo getInstance() {
|
public static MapWithAILayerInfo getInstance() {
|
||||||
AtomicBoolean finished;
|
final AtomicBoolean finished = new AtomicBoolean();
|
||||||
synchronized (DEFAULT_LAYER_SITES) {
|
synchronized (DEFAULT_LAYER_SITES) {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
finished = new AtomicBoolean();
|
instance = new MapWithAILayerInfo(() -> {
|
||||||
instance = new MapWithAILayerInfo(() -> finished.set(true));
|
synchronized (finished) {
|
||||||
|
finished.set(true);
|
||||||
|
finished.notifyAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
finished = null;
|
finished.set(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Avoid a deadlock in the EDT.
|
// Avoid a deadlock in the EDT.
|
||||||
if (finished != null && !SwingUtilities.isEventDispatchThread()) {
|
if (!finished.get() && !SwingUtilities.isEventDispatchThread()) {
|
||||||
while (!finished.get()) {
|
synchronized (finished) {
|
||||||
try {
|
while (!finished.get()) {
|
||||||
Thread.sleep(100);
|
try {
|
||||||
} catch (InterruptedException e) {
|
finished.wait();
|
||||||
Logging.error(e);
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Logging.error(e);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,7 +177,12 @@ public class MapWithAILayerInfo {
|
||||||
layers.removeIf(i -> i.getUrl().contains("localhost:8111"));
|
layers.removeIf(i -> i.getUrl().contains("localhost:8111"));
|
||||||
Collections.sort(layers);
|
Collections.sort(layers);
|
||||||
}
|
}
|
||||||
loadDefaults(false, MainApplication.worker, fastFail, listener);
|
// Ensure that the cache is initialized prior to running in the fork join pool
|
||||||
|
// on webstart
|
||||||
|
if (System.getSecurityManager() != null) {
|
||||||
|
ESRISourceReader.SOURCE_CACHE.getClass();
|
||||||
|
}
|
||||||
|
loadDefaults(false, ForkJoinPool.commonPool(), fastFail, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -188,18 +201,53 @@ public class MapWithAILayerInfo {
|
||||||
* @since 12634
|
* @since 12634
|
||||||
*/
|
*/
|
||||||
public void loadDefaults(boolean clearCache, ExecutorService worker, boolean fastFail, FinishListener listener) {
|
public void loadDefaults(boolean clearCache, ExecutorService worker, boolean fastFail, FinishListener listener) {
|
||||||
final DefaultEntryLoader loader = new DefaultEntryLoader(clearCache, fastFail, listener);
|
final DefaultEntryLoader loader = new DefaultEntryLoader(clearCache, fastFail);
|
||||||
|
if (this.finishListenerListenerList == null) {
|
||||||
|
this.finishListenerListenerList = ListenerList.create();
|
||||||
|
}
|
||||||
|
if (listener != null) {
|
||||||
|
this.finishListenerListenerList.addListener(listener);
|
||||||
|
}
|
||||||
if (worker == null) {
|
if (worker == null) {
|
||||||
loader.realRun();
|
PleaseWaitRunnable pleaseWaitRunnable = new PleaseWaitRunnable(tr("Update default entries")) {
|
||||||
|
@Override
|
||||||
|
protected void cancel() {
|
||||||
|
loader.canceled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void realRun() {
|
||||||
|
loader.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finish() {
|
||||||
|
loader.finish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pleaseWaitRunnable.run();
|
||||||
} else {
|
} else {
|
||||||
worker.execute(loader);
|
worker.execute(loader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for when the data finishes updating
|
||||||
|
*
|
||||||
|
* @param finishListener The listener
|
||||||
|
*/
|
||||||
|
public void addFinishListener(final FinishListener finishListener) {
|
||||||
|
if (this.finishListenerListenerList == null) {
|
||||||
|
finishListener.onFinish();
|
||||||
|
} else {
|
||||||
|
this.finishListenerListenerList.addListener(finishListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loader/updater of the available imagery entries
|
* Loader/updater of the available imagery entries
|
||||||
*/
|
*/
|
||||||
class DefaultEntryLoader extends PleaseWaitRunnable {
|
class DefaultEntryLoader implements Runnable {
|
||||||
|
|
||||||
private final boolean clearCache;
|
private final boolean clearCache;
|
||||||
private final boolean fastFail;
|
private final boolean fastFail;
|
||||||
|
@ -207,30 +255,28 @@ public class MapWithAILayerInfo {
|
||||||
private MapWithAISourceReader reader;
|
private MapWithAISourceReader reader;
|
||||||
private boolean canceled;
|
private boolean canceled;
|
||||||
private boolean loadError;
|
private boolean loadError;
|
||||||
private final FinishListener listener;
|
|
||||||
|
|
||||||
DefaultEntryLoader(boolean clearCache, boolean fastFail, FinishListener listener) {
|
DefaultEntryLoader(boolean clearCache, boolean fastFail) {
|
||||||
super(tr("Update default entries"));
|
|
||||||
this.clearCache = clearCache;
|
this.clearCache = clearCache;
|
||||||
this.fastFail = fastFail;
|
this.fastFail = fastFail;
|
||||||
this.listener = listener;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void cancel() {
|
protected void cancel() {
|
||||||
canceled = true;
|
canceled = true;
|
||||||
Utils.close(reader);
|
Utils.close(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void run() {
|
||||||
protected void realRun() {
|
if (this.clearCache) {
|
||||||
|
ESRISourceReader.SOURCE_CACHE.clear();
|
||||||
|
}
|
||||||
for (String source : getImageryLayersSites()) {
|
for (String source : getImageryLayersSites()) {
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loadSource(source);
|
loadSource(source);
|
||||||
}
|
}
|
||||||
GuiHelper.runInEDT(this::finish);
|
this.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void loadSource(String source) {
|
protected void loadSource(String source) {
|
||||||
|
@ -242,6 +288,9 @@ public class MapWithAILayerInfo {
|
||||||
reader = new MapWithAISourceReader(source);
|
reader = new MapWithAISourceReader(source);
|
||||||
reader.setFastFail(fastFail);
|
reader.setFastFail(fastFail);
|
||||||
Collection<MapWithAIInfo> result = reader.parse();
|
Collection<MapWithAIInfo> result = reader.parse();
|
||||||
|
// This is called here to "pre-cache" the layer information, to avoid blocking
|
||||||
|
// the EDT
|
||||||
|
this.updateEsriLayers(result);
|
||||||
newLayers.addAll(result);
|
newLayers.addAll(result);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
loadError = true;
|
loadError = true;
|
||||||
|
@ -249,18 +298,26 @@ public class MapWithAILayerInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
protected void finish() {
|
* Update the esri layer information
|
||||||
defaultLayers.clear();
|
*
|
||||||
allDefaultLayers.clear();
|
* @param layers The layers to update
|
||||||
defaultLayers.addAll(newLayers);
|
*/
|
||||||
for (MapWithAIInfo layer : newLayers) {
|
private void updateEsriLayers(@Nonnull final Collection<MapWithAIInfo> layers) {
|
||||||
|
for (MapWithAIInfo layer : layers) {
|
||||||
if (MapWithAIType.ESRI == layer.getSourceType()) {
|
if (MapWithAIType.ESRI == layer.getSourceType()) {
|
||||||
allDefaultLayers.addAll(parseEsri(layer));
|
allDefaultLayers.addAll(parseEsri(layer));
|
||||||
} else {
|
} else {
|
||||||
allDefaultLayers.add(layer);
|
allDefaultLayers.add(layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void finish() {
|
||||||
|
defaultLayers.clear();
|
||||||
|
allDefaultLayers.clear();
|
||||||
|
defaultLayers.addAll(newLayers);
|
||||||
|
this.updateEsriLayers(newLayers);
|
||||||
defaultLayerIds.clear();
|
defaultLayerIds.clear();
|
||||||
|
|
||||||
Collections.sort(defaultLayers, new MapWithAIInfo.MapWithAIInfoCategoryComparator());
|
Collections.sort(defaultLayers, new MapWithAIInfo.MapWithAIInfoCategoryComparator());
|
||||||
|
@ -271,8 +328,10 @@ public class MapWithAILayerInfo {
|
||||||
if (!loadError && !defaultLayerIds.isEmpty()) {
|
if (!loadError && !defaultLayerIds.isEmpty()) {
|
||||||
dropOldEntries();
|
dropOldEntries();
|
||||||
}
|
}
|
||||||
if (listener != null) {
|
final ListenerList<FinishListener> listenerList = MapWithAILayerInfo.this.finishListenerListenerList;
|
||||||
listener.onFinish();
|
MapWithAILayerInfo.this.finishListenerListenerList = null;
|
||||||
|
if (listenerList != null) {
|
||||||
|
listenerList.fireEvent(FinishListener::onFinish);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,8 +342,8 @@ public class MapWithAILayerInfo {
|
||||||
* @return The Feature Servers for the ESRI layer
|
* @return The Feature Servers for the ESRI layer
|
||||||
*/
|
*/
|
||||||
private Collection<MapWithAIInfo> parseEsri(MapWithAIInfo layer) {
|
private Collection<MapWithAIInfo> parseEsri(MapWithAIInfo layer) {
|
||||||
try (ESRISourceReader esriReader = new ESRISourceReader(layer)) {
|
try {
|
||||||
return esriReader.parse();
|
return new ESRISourceReader(layer).parse();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Logging.error(e);
|
Logging.error(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.swing.table.DefaultTableModel;
|
import javax.swing.table.DefaultTableModel;
|
||||||
|
|
||||||
|
import org.openstreetmap.josm.gui.util.GuiHelper;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAICategory;
|
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAICategory;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
|
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
|
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
|
||||||
|
@ -49,6 +50,7 @@ class MapWithAIDefaultLayerTableModel extends DefaultTableModel {
|
||||||
columnDataRetrieval.add(i -> i.getAttributionText(0, null, null));
|
columnDataRetrieval.add(i -> i.getAttributionText(0, null, null));
|
||||||
columnDataRetrieval.add(info -> Optional.ofNullable(info.getTermsOfUseURL()).orElse(""));
|
columnDataRetrieval.add(info -> Optional.ofNullable(info.getTermsOfUseURL()).orElse(""));
|
||||||
columnDataRetrieval.add(i -> MapWithAILayerInfo.getInstance().getLayers().contains(i));
|
columnDataRetrieval.add(i -> MapWithAILayerInfo.getInstance().getLayers().contains(i));
|
||||||
|
MapWithAILayerInfo.getInstance().addFinishListener(() -> GuiHelper.runInEDT(this::fireTableDataChanged));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,12 +60,15 @@ class MapWithAIDefaultLayerTableModel extends DefaultTableModel {
|
||||||
* @return The imagery info at the given row number
|
* @return The imagery info at the given row number
|
||||||
*/
|
*/
|
||||||
public static MapWithAIInfo getRow(int row) {
|
public static MapWithAIInfo getRow(int row) {
|
||||||
|
if (row == 0 && MapWithAILayerInfo.getInstance().getAllDefaultLayers().isEmpty()) {
|
||||||
|
return new MapWithAIInfo(tr("Loading"), "");
|
||||||
|
}
|
||||||
return MapWithAILayerInfo.getInstance().getAllDefaultLayers().get(row);
|
return MapWithAILayerInfo.getInstance().getAllDefaultLayers().get(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRowCount() {
|
public int getRowCount() {
|
||||||
return MapWithAILayerInfo.getInstance().getAllDefaultLayers().size();
|
return Math.max(MapWithAILayerInfo.getInstance().getAllDefaultLayers().size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -76,7 +81,7 @@ class MapWithAIDefaultLayerTableModel extends DefaultTableModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getValueAt(int row, int column) {
|
public Object getValueAt(int row, int column) {
|
||||||
MapWithAIInfo info = MapWithAILayerInfo.getInstance().getAllDefaultLayers().get(row);
|
MapWithAIInfo info = getRow(row);
|
||||||
if (column < columnDataRetrieval.size()) {
|
if (column < columnDataRetrieval.size()) {
|
||||||
return columnDataRetrieval.get(column).apply(info);
|
return columnDataRetrieval.get(column).apply(info);
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,8 +190,8 @@ public class MapWithAIProvidersPanel extends JPanel {
|
||||||
if (MapWithAILayerTableModel.contains(info)) {
|
if (MapWithAILayerTableModel.contains(info)) {
|
||||||
Color t = IMAGERY_BACKGROUND_COLOR.get();
|
Color t = IMAGERY_BACKGROUND_COLOR.get();
|
||||||
GuiHelper.setBackgroundReadable(label, isSelected ? t.darker() : t);
|
GuiHelper.setBackgroundReadable(label, isSelected ? t.darker() : t);
|
||||||
} else if (this.area != null && (this.area.intersects(info.getBounds())
|
} else if (this.area != null && info.getBounds() != null
|
||||||
|| (info.getBounds() != null && info.getBounds().intersects(this.area)))) {
|
&& (this.area.intersects(info.getBounds()) || (info.getBounds().intersects(this.area)))) {
|
||||||
Color t = MAPWITHAI_AREA_BACKGROUND_COLOR.get();
|
Color t = MAPWITHAI_AREA_BACKGROUND_COLOR.get();
|
||||||
GuiHelper.setBackgroundReadable(label, isSelected ? t.darker() : t);
|
GuiHelper.setBackgroundReadable(label, isSelected ? t.darker() : t);
|
||||||
} else {
|
} else {
|
||||||
|
@ -657,7 +657,8 @@ public class MapWithAIProvidersPanel extends JPanel {
|
||||||
info.setSourceType(MapWithAIType.THIRD_PARTY);
|
info.setSourceType(MapWithAIType.THIRD_PARTY);
|
||||||
}
|
}
|
||||||
if (MapWithAIType.ESRI == info.getSourceType()) {
|
if (MapWithAIType.ESRI == info.getSourceType()) {
|
||||||
try (ESRISourceReader reader = new ESRISourceReader(info)) {
|
final ESRISourceReader reader = new ESRISourceReader(info);
|
||||||
|
try {
|
||||||
for (MapWithAIInfo i : reader.parse()) {
|
for (MapWithAIInfo i : reader.parse()) {
|
||||||
activeModel.addRow(i);
|
activeModel.addRow(i);
|
||||||
}
|
}
|
||||||
|
@ -845,11 +846,11 @@ public class MapWithAIProvidersPanel extends JPanel {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent evt) {
|
public void actionPerformed(ActionEvent evt) {
|
||||||
MapWithAILayerInfo.getInstance().loadDefaults(true, MainApplication.worker, false, () -> {
|
MapWithAILayerInfo.getInstance().loadDefaults(true, MainApplication.worker, false, () -> {
|
||||||
defaultModel.fireTableDataChanged();
|
GuiHelper.runInEDT(defaultModel::fireTableDataChanged);
|
||||||
defaultTable.getSelectionModel().clearSelection();
|
GuiHelper.runInEDT(defaultTable.getSelectionModel()::clearSelection);
|
||||||
defaultTableListener.clearMap();
|
GuiHelper.runInEDT(defaultTableListener::clearMap);
|
||||||
/* loading new file may change active layers */
|
/* loading new file may change active layers */
|
||||||
activeModel.fireTableDataChanged();
|
GuiHelper.runInEDT(activeModel::fireTableDataChanged);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
// License: GPL. For details, see LICENSE file.
|
// License: GPL. For details, see LICENSE file.
|
||||||
package org.openstreetmap.josm.plugins.mapwithai.io.mapwithai;
|
package org.openstreetmap.josm.plugins.mapwithai.io.mapwithai;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.Closeable;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.json.Json;
|
import javax.json.Json;
|
||||||
import javax.json.JsonArray;
|
import javax.json.JsonArray;
|
||||||
import javax.json.JsonNumber;
|
import javax.json.JsonNumber;
|
||||||
|
@ -21,23 +26,30 @@ import javax.json.JsonReader;
|
||||||
import javax.json.JsonString;
|
import javax.json.JsonString;
|
||||||
import javax.json.JsonStructure;
|
import javax.json.JsonStructure;
|
||||||
import javax.json.JsonValue;
|
import javax.json.JsonValue;
|
||||||
|
import javax.json.stream.JsonParsingException;
|
||||||
|
|
||||||
|
import org.openstreetmap.josm.data.cache.JCSCacheManager;
|
||||||
import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
|
import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
|
||||||
import org.openstreetmap.josm.io.CachedFile;
|
import org.openstreetmap.josm.io.CachedFile;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAICategory;
|
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAICategory;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
|
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIType;
|
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIType;
|
||||||
|
import org.openstreetmap.josm.spi.preferences.Config;
|
||||||
import org.openstreetmap.josm.tools.HttpClient;
|
import org.openstreetmap.josm.tools.HttpClient;
|
||||||
import org.openstreetmap.josm.tools.Logging;
|
import org.openstreetmap.josm.tools.Logging;
|
||||||
import org.openstreetmap.josm.tools.Utils;
|
|
||||||
|
import org.apache.commons.jcs3.access.CacheAccess;
|
||||||
|
import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take a {@link MapWithAIInfo.MapWithAIType#ESRI} layer and convert it to a
|
* Take a {@link MapWithAIInfo.MapWithAIType#ESRI} layer and convert it to a
|
||||||
* list of "true" layers.
|
* list of "true" layers.
|
||||||
*/
|
*/
|
||||||
public class ESRISourceReader implements Closeable {
|
public class ESRISourceReader {
|
||||||
|
/** The cache storing ESRI source information (json) */
|
||||||
|
public static final CacheAccess<String, String> SOURCE_CACHE = JCSCacheManager.getCache("mapwithai:esrisources", 5,
|
||||||
|
50_000, new File(Config.getDirs().getCacheDirectory(true), "mapwithai").getPath());
|
||||||
private final MapWithAIInfo source;
|
private final MapWithAIInfo source;
|
||||||
private final List<CachedFile> cachedFiles = new ArrayList<>();
|
|
||||||
private boolean fastFail;
|
private boolean fastFail;
|
||||||
private final List<MapWithAICategory> ignoreConflationCategories;
|
private final List<MapWithAICategory> ignoreConflationCategories;
|
||||||
private static final String JSON_QUERY_PARAM = "?f=json";
|
private static final String JSON_QUERY_PARAM = "?f=json";
|
||||||
|
@ -84,11 +96,13 @@ public class ESRISourceReader implements Closeable {
|
||||||
String next = "1";
|
String next = "1";
|
||||||
String searchUrl = startReplace.matcher(search).replaceAll(next);
|
String searchUrl = startReplace.matcher(search).replaceAll(next);
|
||||||
while (!next.equals("-1")) {
|
while (!next.equals("-1")) {
|
||||||
try (CachedFile layers = new CachedFile(url + "content/groups/" + group + searchUrl);
|
final String finalUrl = url + "content/groups/" + group + searchUrl;
|
||||||
BufferedReader i = layers.getContentReader();
|
final String jsonString = getJsonString(finalUrl, this.fastFail);
|
||||||
JsonReader reader = Json.createReader(i)) {
|
if (jsonString == null) {
|
||||||
cachedFiles.add(layers);
|
continue;
|
||||||
layers.setFastFail(fastFail);
|
}
|
||||||
|
try (JsonReader reader = Json
|
||||||
|
.createReader(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)))) {
|
||||||
JsonStructure parser = reader.read();
|
JsonStructure parser = reader.read();
|
||||||
if (parser.getValueType() == JsonValue.ValueType.OBJECT) {
|
if (parser.getValueType() == JsonValue.ValueType.OBJECT) {
|
||||||
JsonObject obj = parser.asJsonObject();
|
JsonObject obj = parser.asJsonObject();
|
||||||
|
@ -102,7 +116,7 @@ public class ESRISourceReader implements Closeable {
|
||||||
information.add(parse(feature));
|
information.add(parse(feature));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ClassCastException | IOException e) {
|
} catch (ClassCastException e) {
|
||||||
Logging.error(e);
|
Logging.error(e);
|
||||||
next = "-1";
|
next = "-1";
|
||||||
}
|
}
|
||||||
|
@ -115,6 +129,59 @@ public class ESRISourceReader implements Closeable {
|
||||||
return information;
|
return information;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the json string for a URL
|
||||||
|
*
|
||||||
|
* @param url The URL to get
|
||||||
|
* @param fastFail Fail fast (1 second)
|
||||||
|
* @return The json string, or {@code null}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static String getJsonString(@Nonnull final String url, final boolean fastFail) {
|
||||||
|
String jsonString = SOURCE_CACHE.get(url);
|
||||||
|
// TODO FIXME remove sometime after January 2022 (give it a chance to cleanup
|
||||||
|
// directories)
|
||||||
|
CachedFile.cleanup(url);
|
||||||
|
if (jsonString == null) {
|
||||||
|
synchronized (SOURCE_CACHE) {
|
||||||
|
jsonString = SOURCE_CACHE.get(url);
|
||||||
|
if (jsonString == null) {
|
||||||
|
HttpClient client = null;
|
||||||
|
try {
|
||||||
|
client = HttpClient.create(new URL(url));
|
||||||
|
if (fastFail) {
|
||||||
|
client.setReadTimeout(1000);
|
||||||
|
}
|
||||||
|
final HttpClient.Response response = client.connect();
|
||||||
|
jsonString = response.fetchContent();
|
||||||
|
if (jsonString != null && response.getResponseCode() < 400
|
||||||
|
&& response.getResponseCode() >= 200) {
|
||||||
|
final IElementAttributes elementAttributes = SOURCE_CACHE.getDefaultElementAttributes();
|
||||||
|
// getExpiration returns milliseconds
|
||||||
|
final long expirationTime = response.getExpiration();
|
||||||
|
if (expirationTime > 0) {
|
||||||
|
elementAttributes.setMaxLife(response.getExpiration());
|
||||||
|
} else {
|
||||||
|
elementAttributes.setMaxLife(TimeUnit.SECONDS.toMillis(
|
||||||
|
Config.getPref().getLong("mirror.maxtime", TimeUnit.DAYS.toSeconds(7))));
|
||||||
|
}
|
||||||
|
SOURCE_CACHE.put(url, jsonString, elementAttributes);
|
||||||
|
} else {
|
||||||
|
jsonString = null;
|
||||||
|
}
|
||||||
|
} catch (final IOException e) {
|
||||||
|
Logging.error(e);
|
||||||
|
} finally {
|
||||||
|
if (client != null) {
|
||||||
|
client.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsonString;
|
||||||
|
}
|
||||||
|
|
||||||
private MapWithAIInfo parse(JsonObject feature) {
|
private MapWithAIInfo parse(JsonObject feature) {
|
||||||
// Use the initial esri server information to keep conflation info
|
// Use the initial esri server information to keep conflation info
|
||||||
MapWithAIInfo newInfo = new MapWithAIInfo(source);
|
MapWithAIInfo newInfo = new MapWithAIInfo(source);
|
||||||
|
@ -168,42 +235,60 @@ public class ESRISourceReader implements Closeable {
|
||||||
return (newInfo);
|
return (newInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String featureService(MapWithAIInfo mapwithaiInfo, String url) {
|
/**
|
||||||
String toGet = url.endsWith(JSON_QUERY_PARAM) ? url : url.concat(JSON_QUERY_PARAM);
|
* Get feature service information
|
||||||
try (CachedFile featureServer = new CachedFile(toGet);
|
*
|
||||||
BufferedReader br = featureServer.getContentReader();
|
* @param mapwithaiInfo The info to update
|
||||||
JsonReader reader = Json.createReader(br)) {
|
* @param url The url to get
|
||||||
|
* @return The base url for the feature service
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private String featureService(@Nonnull MapWithAIInfo mapwithaiInfo, @Nonnull String url) {
|
||||||
|
final String toGet = url.endsWith(JSON_QUERY_PARAM) ? url : url.concat(JSON_QUERY_PARAM);
|
||||||
|
final String jsonString = getJsonString(toGet, this.fastFail);
|
||||||
|
if (jsonString == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (JsonReader reader = Json
|
||||||
|
.createReader(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)))) {
|
||||||
JsonObject info = reader.readObject();
|
JsonObject info = reader.readObject();
|
||||||
JsonArray layers = info.getJsonArray("layers");
|
JsonArray layers = info.getJsonArray("layers");
|
||||||
// This fixes #20551
|
// This fixes #20551
|
||||||
if (layers == null || layers.stream().noneMatch(jsonvalue -> jsonvalue != null)) {
|
if (layers == null || layers.stream().noneMatch(Objects::nonNull)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// TODO use all the layers?
|
// TODO use all the layers?
|
||||||
JsonObject layer = layers.stream().filter(jsonValue -> jsonValue != null).findFirst()
|
JsonObject layer = layers.stream().filter(Objects::nonNull).findFirst().orElse(JsonValue.EMPTY_JSON_OBJECT)
|
||||||
.orElse(JsonObject.EMPTY_JSON_OBJECT).asJsonObject();
|
.asJsonObject();
|
||||||
if (layer.containsKey("id")) {
|
if (layer.containsKey("id")) {
|
||||||
String partialUrl = (url.endsWith("/") ? url : url + "/") + layer.getInt("id");
|
String partialUrl = (url.endsWith("/") ? url : url + "/") + layer.getInt("id");
|
||||||
mapwithaiInfo.setReplacementTags(getReplacementTags(partialUrl));
|
mapwithaiInfo.setReplacementTags(getReplacementTags(partialUrl));
|
||||||
|
|
||||||
return partialUrl;
|
return partialUrl;
|
||||||
}
|
}
|
||||||
return null;
|
} catch (JsonParsingException e) {
|
||||||
} catch (IOException e) {
|
|
||||||
Logging.error(e);
|
Logging.error(e);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, String> getReplacementTags(String layerUrl) {
|
/**
|
||||||
|
* Get the replacement tags for a feature service
|
||||||
|
*
|
||||||
|
* @param layerUrl The service url
|
||||||
|
* @return A map of replacement tags
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
private Map<String, String> getReplacementTags(@Nonnull String layerUrl) {
|
||||||
String toGet = layerUrl.endsWith(JSON_QUERY_PARAM) ? layerUrl : layerUrl.concat(JSON_QUERY_PARAM);
|
String toGet = layerUrl.endsWith(JSON_QUERY_PARAM) ? layerUrl : layerUrl.concat(JSON_QUERY_PARAM);
|
||||||
try (CachedFile featureServer = new CachedFile(toGet);
|
final String jsonString = getJsonString(toGet, this.fastFail);
|
||||||
BufferedReader br = featureServer.getContentReader();
|
if (jsonString != null) {
|
||||||
JsonReader reader = Json.createReader(br)) {
|
try (JsonReader reader = Json
|
||||||
return reader.readObject().getJsonArray("fields").getValuesAs(JsonObject.class).stream()
|
.createReader(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)))) {
|
||||||
.collect(Collectors.toMap(o -> o.getString("name"), ESRISourceReader::getReplacementTag));
|
return reader.readObject().getJsonArray("fields").getValuesAs(JsonObject.class).stream()
|
||||||
} catch (IOException e) {
|
.collect(Collectors.toMap(o -> o.getString("name"), ESRISourceReader::getReplacementTag));
|
||||||
Logging.error(e);
|
}
|
||||||
}
|
}
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
@ -225,10 +310,4 @@ public class ESRISourceReader implements Closeable {
|
||||||
public void setFastFail(boolean fastFail) {
|
public void setFastFail(boolean fastFail) {
|
||||||
this.fastFail = fastFail;
|
this.fastFail = fastFail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
cachedFiles.forEach(Utils::close);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
|
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
|
||||||
|
@ -19,6 +21,16 @@ class ESRISourceReaderTest {
|
||||||
@RegisterExtension
|
@RegisterExtension
|
||||||
MapWithAITestRules rule = (MapWithAITestRules) new MapWithAITestRules().wiremock().projection();
|
MapWithAITestRules rule = (MapWithAITestRules) new MapWithAITestRules().wiremock().projection();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
ESRISourceReader.SOURCE_CACHE.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
this.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that ESRI servers are properly added
|
* Test that ESRI servers are properly added
|
||||||
*
|
*
|
||||||
|
@ -33,15 +45,14 @@ class ESRISourceReaderTest {
|
||||||
String tUrl = rule.getWireMock().baseUrl() + "/sharing/rest";
|
String tUrl = rule.getWireMock().baseUrl() + "/sharing/rest";
|
||||||
for (String url : Arrays.asList(tUrl, tUrl + "/")) {
|
for (String url : Arrays.asList(tUrl, tUrl + "/")) {
|
||||||
info.setUrl(url);
|
info.setUrl(url);
|
||||||
try (ESRISourceReader reader = new ESRISourceReader(info)) {
|
final ESRISourceReader reader = new ESRISourceReader(info);
|
||||||
Collection<MapWithAIInfo> layers = reader.parse();
|
Collection<MapWithAIInfo> layers = reader.parse();
|
||||||
assertFalse(layers.isEmpty(), "There should be a MapWithAI layer");
|
assertFalse(layers.isEmpty(), "There should be a MapWithAI layer");
|
||||||
assertTrue(layers.stream().noneMatch(i -> info.getUrl().equals(i.getUrl())),
|
assertTrue(layers.stream().noneMatch(i -> info.getUrl().equals(i.getUrl())),
|
||||||
"The ESRI server should be expanded to feature servers");
|
"The ESRI server should be expanded to feature servers");
|
||||||
assertTrue(layers.stream().allMatch(i -> MapWithAIType.ESRI_FEATURE_SERVER.equals(i.getSourceType())),
|
assertTrue(layers.stream().allMatch(i -> MapWithAIType.ESRI_FEATURE_SERVER.equals(i.getSourceType())),
|
||||||
"There should only be ESRI feature servers");
|
"There should only be ESRI feature servers");
|
||||||
assertEquals(24, layers.size());
|
assertEquals(24, layers.size());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.awaitility.Awaitility;
|
||||||
import org.awaitility.Durations;
|
import org.awaitility.Durations;
|
||||||
import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation;
|
import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation;
|
||||||
import org.junit.runners.model.InitializationError;
|
import org.junit.runners.model.InitializationError;
|
||||||
|
|
||||||
import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
|
import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
|
||||||
import org.openstreetmap.josm.gui.util.GuiHelper;
|
import org.openstreetmap.josm.gui.util.GuiHelper;
|
||||||
import org.openstreetmap.josm.io.OsmApi;
|
import org.openstreetmap.josm.io.OsmApi;
|
||||||
|
|
Ładowanie…
Reference in New Issue