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 069 JsonParser parser = mapper.getFactory().createParser(new BufferedInputStream(con.getInputStream())); 070 if(parser.nextToken() != JsonToken.START_ARRAY) { 071 parser.close(); 072 throw new IllegalStateException("Expected an array"); 073 } 074 075 076 StreetsideImage previous = null; 077 078 while (parser.nextToken() == JsonToken.START_OBJECT) { 079 // read everything from this START_OBJECT to the matching END_OBJECT 080 // and return it as a tree model ObjectNode 081 ObjectNode node = mapper.readTree(parser); 082 // Discard the first sequence ('enabled') - it does not contain bubble data 083 if (node.get("id") != null && node.get("la") != null && node.get("lo") != null) { 084 StreetsideImage image = new StreetsideImage(CubemapUtils.convertDecimal2Quaternary(node.path("id").asLong()), node.path("la").asDouble(), node.get("lo").asDouble()); 085 if(previous!=null) { 086 // Analyze sequence behaviour 087 //previous.setNext(image.) 088 } 089 image.setAd(node.path("ad").asInt()); 090 image.setAl(node.path("al").asDouble()); 091 image.setBl(node.path("bl").asText()); 092 image.setCd(node.path("cd").asLong()); 093 image.setHe(node.path("he").asDouble()); 094 image.setMl(node.path("ml").asInt()); 095 image.setNbn(node.findValuesAsText("nbn")); 096 image.setNe(node.path("ne").asLong()); 097 image.setPbn(node.findValuesAsText("pbn")); 098 image.setPi(node.path("pi").asDouble()); 099 image.setPr(node.path("pr").asLong()); 100 // TODO: inner class @rrh 101 // image.setRn(node.path("rn").asText()); 102 image.setRo(node.path("ro").asDouble()); 103 104 // Add list of cubemap tile images to images 105 List<StreetsideImage> tiles = new ArrayList<StreetsideImage>(); 106 107 // TODO: set previous and next @rrh 108 109 EnumSet.allOf(CubemapUtils.CubemapFaces.class).forEach(face -> { 110 111 for (int i = 0; i < 4; i++) { 112 // Initialize four-tiled cubemap faces (four images per cube side with 18-length 113 // Quadkey) 114 //if (StreetsideProperties.CUBEFACE_SIZE.get().intValue() == 4) { 115 if (!StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) { 116 StreetsideImage tile = new StreetsideImage( 117 String.valueOf(image.getId() + Integer.valueOf(i))); 118 tiles.add(tile); 119 } 120 // Initialize four-tiled cubemap faces (four images per cub eside with 20-length 121 // Quadkey) 122 //if (StreetsideProperties.CUBEFACE_SIZE.get().intValue() == 16) { 123 if (StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) { 124 for (int j = 0; j < 4; j++) { 125 StreetsideImage tile = new StreetsideImage(String.valueOf(image.getId() 126 + face.getValue() + CubemapUtils.rowCol2StreetsideCellAddressMap 127 .get(String.valueOf(Integer.valueOf(i).toString() + Integer.valueOf(j).toString())))); 128 tiles.add(tile); 129 } 130 } 131 } 132 }); 133 134 bubbleImages.add(image); 135 Logging.info("Added image with id <" + image.getId() + ">"); 136 // TODO: double check whether this pre-caches successfullly @rrh 137 //StreetsideData.downloadSurroundingCubemaps(image); 138 139 } 140 } 141 142 parser.close(); 143 144 //StreetsideImage[] images; 145 146 // First load all of the 'bubbles' from the request as Streetside Images 147 /*List<StreetsideImage> images = mapper 148 .readValue(new BufferedInputStream(con.getInputStream()), new TypeReference<List<StreetsideImage>>() {}); 149 */ 150 151 152 //images = mapper.readValue(new BufferedInputStream(con.getInputStream()), StreetsideImage[].class); 153 154 /*for (StreetsideImage image : bubbleImages) { 155 image = JsonStreetsideSequencesDecoder.decodeBubbleData(image); 156 if(image != null) bubbleImages.add(image); 157 }*/ 158 159 } catch (JsonParseException e) { 160 e.printStackTrace(); 161 } catch (JsonMappingException e) { 162 e.printStackTrace(); 163 } catch (IOException e) { 164 e.printStackTrace(); 165 } 166 167 /** Top Level Bubble Metadata in Streetside are bubble (aka images) not Sequences 168 * so a sequence needs to be created and have images added to it. If the distribution 169 * of Streetside images is non-sequential, the Mapillary "Walking Action" may behave 170 * unpredictably. 171 **/ 172 seq.add(bubbleImages); 173 174 if (StreetsideProperties.CUT_OFF_SEQUENCES_AT_BOUNDS.get()) { 175 for (StreetsideAbstractImage img : seq.getImages()) { 176 if (bounds.contains(img.getLatLon())) { 177 data.add(img); 178 } else { 179 seq.remove(img); 180 } 181 } 182 } else { 183 boolean sequenceCrossesThroughBounds = false; 184 for (int i = 0; i < seq.getImages().size() && !sequenceCrossesThroughBounds; i++) { 185 sequenceCrossesThroughBounds = bounds.contains(seq.getImages().get(i).getLatLon()); 186 } 187 if (sequenceCrossesThroughBounds) { 188 data.addAll(seq.getImages(), true); 189 } 190 } 191 192 final long endTime = System.currentTimeMillis(); 193 Logging.debug(I18n.tr("Sucessfully loaded {0} Microsoft Streetside images in {0} ",seq.getImages().size(),endTime-startTime%60)); 194 } 195 196 @Override 197 protected Function<Bounds, URL> getUrlGenerator() { 198 return URL_GEN; 199 } 200}