Skip to content

Commit

Permalink
see #15182 - make JOSM callable as standalone validator (patch by tay…
Browse files Browse the repository at this point in the history
…lor.smock)

git-svn-id: https://josm.openstreetmap.de/svn/trunk@18365 0c6e7542-c601-0410-84e7-c038aed88b3b
  • Loading branch information
don-vip committed Jan 25, 2022
1 parent e7f33a6 commit 3442d7d
Show file tree
Hide file tree
Showing 13 changed files with 1,101 additions and 37 deletions.
471 changes: 471 additions & 0 deletions src/org/openstreetmap/josm/data/validation/ValidatorCLI.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,6 @@ public void check(OsmPrimitive p) {
}
}

/**
* A handler for assertion error messages (for not fulfilled "assertMatch", "assertNoMatch").
*/
@FunctionalInterface
interface AssertionConsumer extends Consumer<String> {
}

/**
* Adds a new MapCSS config file from the given URL.
* @param url The unique URL of the MapCSS config file
Expand All @@ -274,7 +267,18 @@ public synchronized ParseResult addMapCSS(String url) throws ParseException, IOE
return addMapCSS(url, checkAssertions ? Logging::warn : null);
}

synchronized ParseResult addMapCSS(String url, AssertionConsumer assertionConsumer) throws ParseException, IOException {
/**
* Adds a new MapCSS config file from the given URL. <br />
* NOTE: You should prefer {@link #addMapCSS(String)} unless you <i>need</i> to know what the assertions return.
*
* @param url The unique URL of the MapCSS config file
* @param assertionConsumer A string consumer for error messages.
* @return List of tag checks and parsing errors, or null
* @throws ParseException if the config file does not match MapCSS syntax
* @throws IOException if any I/O error occurs
* @since 18365 (public, primarily for ValidatorCLI)
*/
public synchronized ParseResult addMapCSS(String url, Consumer<String> assertionConsumer) throws ParseException, IOException {
CheckParameterUtil.ensureParameterNotNull(url, "url");
ParseResult result;
try (CachedFile cache = new CachedFile(url);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.openstreetmap.josm.command.Command;
Expand Down Expand Up @@ -46,7 +47,7 @@ private MapCSSTagCheckerAsserts() {
* @param assertionConsumer The handler for assertion error messages
*/
static void checkAsserts(final MapCSSTagCheckerRule check, final Map<String, Boolean> assertions,
final MapCSSTagChecker.AssertionConsumer assertionConsumer) {
final Consumer<String> assertionConsumer) {
final DataSet ds = new DataSet();
Logging.debug("Check: {0}", check);
for (final Map.Entry<String, Boolean> i : assertions.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -31,7 +32,6 @@
import org.openstreetmap.josm.data.validation.Severity;
import org.openstreetmap.josm.data.validation.Test;
import org.openstreetmap.josm.data.validation.TestError;
import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.AssertionConsumer;
import org.openstreetmap.josm.gui.mappaint.Environment;
import org.openstreetmap.josm.gui.mappaint.Keyword;
import org.openstreetmap.josm.gui.mappaint.mapcss.Condition;
Expand Down Expand Up @@ -106,7 +106,7 @@ MapCSSTagCheckerRule toImmutable() {

private static final String POSSIBLE_THROWS = "throwError/throwWarning/throwOther";

static MapCSSTagCheckerRule ofMapCSSRule(final MapCSSRule rule, AssertionConsumer assertionConsumer) throws IllegalDataException {
static MapCSSTagCheckerRule ofMapCSSRule(final MapCSSRule rule, Consumer<String> assertionConsumer) throws IllegalDataException {
final MapCSSTagCheckerRule check = new MapCSSTagCheckerRule(rule);
final Map<String, Boolean> assertions = new HashMap<>();
for (Instruction i : rule.declaration.instructions) {
Expand Down Expand Up @@ -185,7 +185,7 @@ static MapCSSTagChecker.ParseResult readMapCSS(Reader css) throws ParseException
return readMapCSS(css, null);
}

static MapCSSTagChecker.ParseResult readMapCSS(Reader css, AssertionConsumer assertionConsumer) throws ParseException {
static MapCSSTagChecker.ParseResult readMapCSS(Reader css, Consumer<String> assertionConsumer) throws ParseException {
CheckParameterUtil.ensureParameterNotNull(css, "css");

final MapCSSStyleSource source = new MapCSSStyleSource("");
Expand Down
5 changes: 4 additions & 1 deletion src/org/openstreetmap/josm/gui/MainApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileSource;
import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
import org.openstreetmap.josm.data.projection.datum.NTV2Proj4DirGridShiftFileSource;
import org.openstreetmap.josm.data.validation.ValidatorCLI;
import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker;
import org.openstreetmap.josm.gui.ProgramArguments.Option;
import org.openstreetmap.josm.gui.SplashScreen.SplashProgressMonitor;
Expand Down Expand Up @@ -311,6 +312,7 @@ public void processArguments(String[] argArray) {
registerCLIModule(JOSM_CLI_MODULE);
registerCLIModule(ProjectionCLI.INSTANCE);
registerCLIModule(RenderingCLI.INSTANCE);
registerCLIModule(ValidatorCLI.INSTANCE);
}

/**
Expand Down Expand Up @@ -660,7 +662,8 @@ static String getHelp() {
tr("commands")+":\n"+
"\trunjosm "+tr("launch JOSM (default, performed when no command is specified)")+'\n'+
"\trender "+tr("render data and save the result to an image file")+'\n'+
"\tproject "+tr("convert coordinates from one coordinate reference system to another")+"\n\n"+
"\tproject " + tr("convert coordinates from one coordinate reference system to another")+ '\n' +
"\tvalidate " + tr("validate data") + "\n\n" +
tr("For details on the {0} and {1} commands, run them with the {2} option.", "render", "project", "--help")+'\n'+
tr("The remainder of this help page documents the {0} command.", "runjosm")+"\n\n"+
tr("options")+":\n"+
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,11 @@ protected void importData(InputStream in, final File associatedFile, ProgressMon
final OsmImporterData data = loadLayer(in, associatedFile,
associatedFile == null ? OsmDataLayer.createNewName() : associatedFile.getName(), pm);

final OsmDataLayer layer = data.getLayer();
// Note: addLayer calls GuiHelper.runInEDTAndWaitWithException
MainApplication.getLayerManager().addLayer(layer);
// FIXME: remove UI stuff from IO subsystem
GuiHelper.runInEDT(() -> {
OsmDataLayer layer = data.getLayer();
MainApplication.getLayerManager().addLayer(layer);
data.getPostLayerTask().run();
data.getLayer().onPostLoadFromFile();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.openstreetmap.josm.data.projection.ProjectionConfigurationException;
import org.openstreetmap.josm.data.projection.Projections;
import org.openstreetmap.josm.gui.ExtendedDialog;
import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
import org.openstreetmap.josm.gui.widgets.HtmlPanel;
Expand Down Expand Up @@ -52,15 +53,15 @@ public CustomProjectionChoice() {

private static class PreferencePanel extends JPanel {

public JosmTextField input;
public AutoCompTextField<String> input;
private HistoryComboBox cbInput;

PreferencePanel(String initialText, ActionListener listener) {
build(initialText, listener);
}

private void build(String initialText, final ActionListener listener) {
input = new JosmTextField(30);
input = new AutoCompTextField<>(30);
cbInput = new HistoryComboBox();
cbInput.setEditor(new BasicComboBoxEditor() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ public void removeCancelListener(CancelListener listener) {
* Ticks handling
==================*/

/**
* Update progress message
* @param value The percentage of completion (this and child progress)
*/
protected abstract void updateProgress(double value);

@Override
Expand Down
93 changes: 93 additions & 0 deletions src/org/openstreetmap/josm/gui/progress/CLIProgressMonitor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.progress;

import static org.openstreetmap.josm.tools.I18n.tr;

import java.awt.Component;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.Stopwatch;
import org.openstreetmap.josm.tools.Utils;

/**
* CLI implementation of a {@link ProgressMonitor}
* @author Taylor Smock
* @since 18365
*/
public class CLIProgressMonitor extends AbstractProgressMonitor {
/** The current task id */
private ProgressTaskId taskId;
/** The current task title */
private String title = "";
/** The custom text (prepended with '/') */
private String customText = "";
/** The last time we updated the progress information */
private Stopwatch lastUpdateTime;
/** The start time of the monitor */
private Stopwatch startTime;

/**
* Create a new {@link CLIProgressMonitor}
*/
public CLIProgressMonitor() {
super(new CancelHandler());
}

@Override
protected void doBeginTask() {
if (!Utils.isBlank(this.title)) {
Logging.info(tr("Beginning task{2}: {0}{1}", this.title, this.customText,
Optional.ofNullable(this.taskId).map(ProgressTaskId::getId).map(id -> ' ' + id).orElse("")));
}
this.startTime = Stopwatch.createStarted();
this.lastUpdateTime = this.startTime;
}

@Override
protected void doFinishTask() {
Logging.info(tr("Finishing task{2}: {0}{1} ({3})", this.title, this.customText,
Optional.ofNullable(this.taskId).map(ProgressTaskId::getId).map(id -> ' ' + id).orElse(""), this.startTime));
this.lastUpdateTime = null;
}

@Override
protected void doSetIntermediate(boolean value) {
// Do nothing for now
}

@Override
protected void doSetTitle(String title) {
this.title = Optional.ofNullable(title).orElse("");
}

@Override
protected void doSetCustomText(String customText) {
this.customText = Optional.ofNullable(customText).map(str -> '/' + str).orElse("");
}

@Override
protected void updateProgress(double value) {
if (this.lastUpdateTime == null || this.lastUpdateTime.elapsed() > TimeUnit.SECONDS.toMillis(10)) {
this.lastUpdateTime = Stopwatch.createStarted();
Logging.info(tr("Progress of task{2}: {0}{1} is {3}% ({4})", this.title, this.customText,
Optional.ofNullable(this.taskId).map(ProgressTaskId::getId).map(id -> ' ' + id).orElse(""), value * 100, this.startTime));
}
}

@Override
public void setProgressTaskId(ProgressTaskId taskId) {
this.taskId = taskId;
}

@Override
public ProgressTaskId getProgressTaskId() {
return this.taskId;
}

@Override
public Component getWindowParent() {
return null;
}
}
2 changes: 1 addition & 1 deletion src/org/openstreetmap/josm/gui/util/GuiHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ public static <V> V runInEDTAndWaitAndReturn(Callable<V> callable) {
* @since 10271
*/
public static void assertCallFromEdt() {
if (!SwingUtilities.isEventDispatchThread()) {
if (!SwingUtilities.isEventDispatchThread() && !GraphicsEnvironment.isHeadless()) {
throw new IllegalStateException(
"Needs to be called from the EDT thread, not from " + Thread.currentThread().getName());
}
Expand Down
90 changes: 90 additions & 0 deletions src/org/openstreetmap/josm/io/GeoJSONMapRouletteWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.io;

import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonValue;

import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.WaySegment;
import org.openstreetmap.josm.data.validation.TestError;
import org.openstreetmap.josm.tools.Logging;

/**
* Convert {@link TestError} to MapRoulette Tasks
* @author Taylor Smock
* @since 18365
*/
public class GeoJSONMapRouletteWriter extends GeoJSONWriter {

/**
* Constructs a new {@code GeoJSONWriter}.
* @param ds The originating OSM dataset
*/
public GeoJSONMapRouletteWriter(DataSet ds) {
super(ds);
super.setOptions(Options.RIGHT_HAND_RULE, Options.WRITE_OSM_INFORMATION);
}

/**
* Convert a test error to a string
* @param testError The test error to convert
* @return The MapRoulette challenge object
*/
public Optional<JsonObject> write(final TestError testError) {
final JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder();
final JsonArrayBuilder featuresBuilder = Json.createArrayBuilder();
final JsonObjectBuilder propertiesBuilder = Json.createObjectBuilder();
propertiesBuilder.add("message", testError.getMessage());
Optional.ofNullable(testError.getDescription()).ifPresent(description -> propertiesBuilder.add("description", description));
propertiesBuilder.add("code", testError.getCode());
propertiesBuilder.add("fixable", testError.isFixable());
propertiesBuilder.add("severity", testError.getSeverity().toString());
propertiesBuilder.add("severityInteger", testError.getSeverity().getLevel());
propertiesBuilder.add("test", testError.getTester().getName());
Stream.concat(testError.getPrimitives().stream(), testError.getHighlighted().stream()).distinct().map(p -> {
if (p instanceof OsmPrimitive) {
return p;
} else if (p instanceof WaySegment) {
return ((WaySegment) p).toWay();
}
Logging.trace("Could not convert {0} to an OsmPrimitive", p);
return null;
}).filter(Objects::nonNull).distinct().map(OsmPrimitive.class::cast)
.forEach(primitive -> super.appendPrimitive(primitive, featuresBuilder));
final JsonArray featureArray = featuresBuilder.build();
final JsonArrayBuilder featuresMessageBuilder = Json.createArrayBuilder();
if (featureArray.isEmpty()) {
Logging.trace("Could not generate task for {0}", testError.getMessage());
return Optional.empty();
}
JsonObject primitive = featureArray.getJsonObject(0);
JsonObjectBuilder replacementPrimitive = Json.createObjectBuilder(primitive);
final JsonObjectBuilder properties;
if (primitive.containsKey("properties") && primitive.get("properties").getValueType() == JsonValue.ValueType.OBJECT) {
properties = Json.createObjectBuilder(primitive.getJsonObject("properties"));
} else {
properties = Json.createObjectBuilder();
}
properties.addAll(propertiesBuilder);
replacementPrimitive.add("properties", properties);
featuresMessageBuilder.add(replacementPrimitive);
for (int i = 1; i < featureArray.size(); i++) {
featuresMessageBuilder.add(featureArray.get(i));
}
// For now, don't add any cooperativeWork objects, as JOSM should be able to find the fixes.
// This should change if the ValidatorCLI can use plugins (especially those introducing external data, like
// the ElevationProfile plugin (which provides elevation data)).
jsonObjectBuilder.add("type", "FeatureCollection");
jsonObjectBuilder.add("features", featuresMessageBuilder);
return Optional.of(jsonObjectBuilder.build());
}
}
Loading

0 comments on commit 3442d7d

Please sign in to comment.