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}