001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.plugins.streetside.io.download; 003 004import java.io.BufferedInputStream; 005import java.io.IOException; 006import java.net.URL; 007import java.net.URLConnection; 008import java.util.ArrayList; 009import java.util.EnumSet; 010import java.util.List; 011import java.util.function.Function; 012 013import org.openstreetmap.josm.data.Bounds; 014import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage; 015import org.openstreetmap.josm.plugins.streetside.StreetsideData; 016import org.openstreetmap.josm.plugins.streetside.StreetsideImage; 017import org.openstreetmap.josm.plugins.streetside.StreetsideSequence; 018import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils; 019import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties; 020import org.openstreetmap.josm.plugins.streetside.utils.StreetsideSequenceIdGenerator; 021import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL.APIv3; 022import org.openstreetmap.josm.tools.I18n; 023import org.openstreetmap.josm.tools.Logging; 024 025import com.fasterxml.jackson.core.JsonParseException; 026import com.fasterxml.jackson.core.JsonParser; 027import com.fasterxml.jackson.core.JsonToken; 028import com.fasterxml.jackson.databind.DeserializationFeature; 029import com.fasterxml.jackson.databind.JsonMappingException; 030import com.fasterxml.jackson.databind.ObjectMapper; 031import com.fasterxml.jackson.databind.node.ObjectNode; 032 033public final class SequenceDownloadRunnable extends BoundsDownloadRunnable { 034 035 private final StreetsideData data; 036 037 private static final Function<Bounds, URL> URL_GEN = APIv3::searchStreetsideSequences; 038 039 public SequenceDownloadRunnable(final StreetsideData data, final Bounds bounds) { 040 super(bounds); 041 this.data = data; 042 } 043 044 @Override 045 public void run(final URLConnection con) throws IOException { 046 if (Thread.interrupted()) { 047 return; 048 } 049 050 StreetsideSequence seq = new StreetsideSequence(StreetsideSequenceIdGenerator.generateId()); 051 052 // TODO: how can LatLon and heading / camera angles (he attribute) be set for a sequence? 053 // and does it make sense? @rrh 054 055 List<StreetsideImage> bubbleImages = new ArrayList<>(); 056 057 final long startTime = System.currentTimeMillis(); 058 059 ObjectMapper mapper = new ObjectMapper(); 060 // Creation of Jackson Object Mapper necessary for Silverlight 2.0 JSON Syntax parsing: 061 // (no double quotes in JSON on attribute names) 062 mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); 063 064 // Allow unrecognized properties - won't break with addition of new attributes 065 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 066 067 try { 068 JsonParser parser = mapper.getFactory().createParser(new BufferedInputStream(con.getInputStream())); 069 if(parser.nextToken() != JsonToken.START_ARRAY) { 070 parser.close(); 071 throw new IllegalStateException("Expected an array"); 072 } 073 074 StreetsideImage previous = null; 075 076 while (parser.nextToken() == JsonToken.START_OBJECT) { 077 // read everything from this START_OBJECT to the matching END_OBJECT 078 // and return it as a tree model ObjectNode 079 ObjectNode node = mapper.readTree(parser); 080 // Discard the first sequence ('enabled') - it does not contain bubble data 081 if (node.get("id") != null && node.get("la") != null && node.get("lo") != null) { 082 StreetsideImage image = new StreetsideImage(CubemapUtils.convertDecimal2Quaternary(node.path("id").asLong()), node.path("la").asDouble(), node.get("lo").asDouble()); 083 if(previous!=null) { 084 // Analyze sequence behaviour 085 //previous.setNext(image.) 086 } 087 image.setAd(node.path("ad").asInt()); 088 image.setAl(node.path("al").asDouble()); 089 image.setBl(node.path("bl").asText()); 090 image.setCd(node.path("cd").asLong()); 091 image.setHe(node.path("he").asDouble()); 092 image.setMl(node.path("ml").asInt()); 093 image.setNbn(node.findValuesAsText("nbn")); 094 image.setNe(node.path("ne").asLong()); 095 image.setPbn(node.findValuesAsText("pbn")); 096 image.setPi(node.path("pi").asDouble()); 097 image.setPr(node.path("pr").asLong()); 098 // TODO: inner class @rrh 099 // image.setRn(node.path("rn").asText()); 100 image.setRo(node.path("ro").asDouble()); 101 102 // Add list of cubemap tile images to images 103 List<StreetsideImage> tiles = new ArrayList<StreetsideImage>(); 104 105 EnumSet.allOf(CubemapUtils.CubemapFaces.class).forEach(face -> { 106 107 for (int i = 0; i < 4; i++) { 108 // Initialize four-tiled cubemap faces (four images per cube side with 18-length 109 // Quadkey) 110 // if (StreetsideProperties.CUBEFACE_SIZE.get().intValue() == 4) { 111 if (!StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) { 112 StreetsideImage tile = new StreetsideImage(String.valueOf(image.getId() + Integer.valueOf(i))); 113 tiles.add(tile); 114 } 115 // Initialize four-tiled cubemap faces (four images per cub eside with 20-length 116 // Quadkey) 117 // if (StreetsideProperties.CUBEFACE_SIZE.get().intValue() == 16) { 118 if (StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) { 119 for (int j = 0; j < 4; j++) { 120 StreetsideImage tile = new StreetsideImage( 121 String.valueOf( 122 image.getId() + face.getValue() + CubemapUtils.rowCol2StreetsideCellAddressMap 123 .get(String.valueOf(Integer.valueOf(i).toString() + Integer.valueOf(j).toString())) 124 )); 125 tiles.add(tile); 126 } 127 } 128 } 129 }); 130 131 bubbleImages.add(image); 132 Logging.info("Added image with id <" + image.getId() + ">"); 133 if (StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get()) { 134 StreetsideData.downloadSurroundingCubemaps(image); 135 } 136 } 137 } 138 139 parser.close(); 140 141 //StreetsideImage[] images; 142 143 // First load all of the 'bubbles' from the request as Streetside Images 144 /*List<StreetsideImage> images = mapper 145 .readValue(new BufferedInputStream(con.getInputStream()), new TypeReference<List<StreetsideImage>>() {}); 146 */ 147 148 149 //images = mapper.readValue(new BufferedInputStream(con.getInputStream()), StreetsideImage[].class); 150 151 /*for (StreetsideImage image : bubbleImages) { 152 image = JsonStreetsideSequencesDecoder.decodeBubbleData(image); 153 if(image != null) bubbleImages.add(image); 154 }*/ 155 156 } catch (JsonParseException e) { 157 e.printStackTrace(); 158 } catch (JsonMappingException e) { 159 e.printStackTrace(); 160 } catch (IOException e) { 161 e.printStackTrace(); 162 } 163 164 /** Top Level Bubble Metadata in Streetside are bubble (aka images) not Sequences 165 * so a sequence needs to be created and have images added to it. If the distribution 166 * of Streetside images is non-sequential, the Mapillary "Walking Action" may behave 167 * unpredictably. 168 **/ 169 seq.add(bubbleImages); 170 171 if (StreetsideProperties.CUT_OFF_SEQUENCES_AT_BOUNDS.get()) { 172 for (StreetsideAbstractImage img : seq.getImages()) { 173 if (bounds.contains(img.getLatLon())) { 174 data.add(img); 175 } else { 176 seq.remove(img); 177 } 178 } 179 } else { 180 boolean sequenceCrossesThroughBounds = false; 181 for (int i = 0; i < seq.getImages().size() && !sequenceCrossesThroughBounds; i++) { 182 sequenceCrossesThroughBounds = bounds.contains(seq.getImages().get(i).getLatLon()); 183 } 184 if (sequenceCrossesThroughBounds) { 185 data.addAll(seq.getImages(), true); 186 } 187 } 188 189 final long endTime = System.currentTimeMillis(); 190 Logging.debug(I18n.tr("Sucessfully loaded {0} Microsoft Streetside images in {0} ",seq.getImages().size(),endTime-startTime%60)); 191 } 192 193 @Override 194 protected Function<Bounds, URL> getUrlGenerator() { 195 return URL_GEN; 196 } 197}