001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.plugins.streetside.utils.api;
003
004import java.awt.Shape;
005import java.awt.geom.Path2D;
006
007import javax.json.JsonArray;
008import javax.json.JsonNumber;
009import javax.json.JsonObject;
010import javax.json.JsonValue;
011
012import org.openstreetmap.josm.plugins.streetside.model.ImageDetection;
013import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL.APIv3;
014import org.openstreetmap.josm.tools.Logging;
015
016
017/**
018 * Decodes the JSON returned by {@link APIv3} into Java objects.
019 * Takes a {@link JsonObject} and {@link #decodeImageDetection(JsonObject)} tries to convert it to a {@link ImageDetection}.
020 */
021public final class JsonImageDetectionDecoder {
022  private JsonImageDetectionDecoder() {
023    // Private constructor to avoid instantiation
024  }
025
026  // TODO: Image detections? Keep? @rrh
027  public static ImageDetection decodeImageDetection(final JsonObject json) {
028    if (json == null || !"Feature".equals(json.getString("type", null))) {
029      return null;
030    }
031
032    final JsonValue properties = json.get("properties");
033    if (properties instanceof JsonObject) {
034      final String key = ((JsonObject) properties).getString("key", null);
035      final String packag = ((JsonObject) properties).getString("package", null);
036      final String imageKey = ((JsonObject) properties).getString("image_key", null);
037      final String value = ((JsonObject) properties).getString("value", null);
038      final JsonValue scoreVal = ((JsonObject) properties).get("score");
039      final Double score = scoreVal instanceof JsonNumber ? ((JsonNumber) scoreVal).doubleValue() : null;
040      final Shape shape = decodeShape(((JsonObject) properties).get("shape"));
041      if (shape instanceof Path2D && imageKey != null && key != null && score != null && packag != null && value != null) {
042        return new ImageDetection((Path2D) shape, imageKey, key, score, packag, value);
043      }
044    }
045    return null;
046  }
047
048  private static Shape decodeShape(JsonValue json) {
049    if (json instanceof JsonObject) {
050      if (!"Polygon".equals(((JsonObject) json).getString("type", null))) {
051        Logging.warn(
052          String.format("Image detections using shapes with type=%s are currently not supported!",
053          ((JsonObject) json).getString("type", "‹no type set›"))
054        );
055      } else {
056        final JsonValue coordinates = ((JsonObject) json).get("coordinates");
057        if (coordinates instanceof JsonArray && !((JsonArray) coordinates).isEmpty()) {
058          return decodePolygon((JsonArray) coordinates);
059        }
060      }
061    }
062    return null;
063  }
064
065  /**
066   * Decodes a polygon (may be a multipolygon) from JSON
067   * @param json the json array to decode, must not be <code>null</code>
068   * @return the decoded polygon as {@link Path2D.Double}
069   */
070  private static Path2D decodePolygon(final JsonArray json) {
071    final Path2D shape = new Path2D.Double();
072    json.forEach(val -> {
073      final Shape part = val instanceof JsonArray ? decodeSimplePolygon((JsonArray) val) : null;
074      if (part != null) {
075        shape.append(part, false);
076      }
077    });
078    if (shape.getCurrentPoint() != null) {
079      return shape;
080    }
081    return null;
082  }
083
084  /**
085   * Decodes a simple polygon (consisting of only one continuous path) from JSON
086   * @param json the json array to decode, must not be <code>null</code>
087   * @return the decoded polygon as {@link Path2D.Double}
088   * @throws NullPointerException if parameter is <code>null</code>
089   */
090  private static Path2D decodeSimplePolygon(final JsonArray json) {
091    final Path2D shape = new Path2D.Double();
092    json.forEach(val -> {
093      double[] coord = JsonDecoder.decodeDoublePair(val instanceof JsonArray ? (JsonArray) val : null);
094      if (shape.getCurrentPoint() == null && coord != null) {
095        shape.moveTo(coord[0], coord[1]);
096      } else if (coord != null) {
097        shape.lineTo(coord[0], coord[1]);
098      }
099    });
100    if (shape.getCurrentPoint() != null) {
101      shape.closePath();
102      return shape;
103    }
104    return null;
105  }
106}