    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}