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}