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