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}