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; 022 023import com.fasterxml.jackson.core.JsonParseException; 024import com.fasterxml.jackson.core.JsonParser; 025import com.fasterxml.jackson.core.JsonToken; 026import com.fasterxml.jackson.databind.DeserializationFeature; 027import com.fasterxml.jackson.databind.JsonMappingException; 028import com.fasterxml.jackson.databind.ObjectMapper; 029import com.fasterxml.jackson.databind.node.ObjectNode; 030 031public final class SequenceDownloadRunnable extends BoundsDownloadRunnable { 032 033 private final StreetsideData data; 034 035 private static final Function<Bounds, URL> URL_GEN = APIv3::searchStreetsideSequences; 036 037 public SequenceDownloadRunnable(final StreetsideData data, final Bounds bounds) { 038 super(bounds); 039 this.data = data; 040 } 041 042 @Override 043 public void run(final URLConnection con) throws IOException { 044 if (Thread.interrupted()) { 045 return; 046 } 047 048 StreetsideSequence seq = new StreetsideSequence(StreetsideSequenceIdGenerator.generateId()); 049 050 List<StreetsideImage> bubbleImages = new ArrayList<>(); 051 052 final long startTime = System.currentTimeMillis(); 053 054 ObjectMapper mapper = new ObjectMapper(); 055 // Creation of Jackson Object Mapper necessary for Silverlight 2.0 JSON Syntax parsing: 056 // (no double quotes in JSON on attribute names) 057 mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); 058 059 // Allow unrecognized properties - won't break with addition of new attributes 060 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 061 062 try { 063 JsonParser parser = mapper.getFactory().createParser(new BufferedInputStream(con.getInputStream())); 064 if(parser.nextToken() != JsonToken.START_ARRAY) { 065 parser.close(); 066 throw new IllegalStateException("Expected an array"); 067 } 068 069 StreetsideImage previous = null; 070 071 while (parser.nextToken() == JsonToken.START_OBJECT) { 072 // read everything from this START_OBJECT to the matching END_OBJECT 073 // and return it as a tree model ObjectNode 074 ObjectNode node = mapper.readTree(parser); 075 // Discard the first sequence ('enabled') - it does not contain bubble data 076 if (node.get("id") != null && node.get("la") != null && node.get("lo") != null) { 077 StreetsideImage image = new StreetsideImage(CubemapUtils.convertDecimal2Quaternary(node.path("id").asLong()), node.path("la").asDouble(), node.get("lo").asDouble()); 078 if(previous!=null) { 079 // Analyze sequence behaviour 080 //previous.setNext(image.getId()); 081 } 082 previous = image; 083 image.setAd(node.path("ad").asInt()); 084 image.setAl(node.path("al").asDouble()); 085 image.setBl(node.path("bl").asText()); 086 image.setCd(node.path("cd").asLong()); 087 image.setHe(node.path("he").asDouble()); 088 image.setMl(node.path("ml").asInt()); 089 image.setNbn(node.findValuesAsText("nbn")); 090 image.setNe(node.path("ne").asLong()); 091 image.setPbn(node.findValuesAsText("pbn")); 092 image.setPi(node.path("pi").asDouble()); 093 image.setPr(node.path("pr").asLong()); 094 image.setRo(node.path("ro").asDouble()); 095 096 // Add list of cubemap tile images to images 097 List<StreetsideImage> tiles = new ArrayList<>(); 098 099 EnumSet.allOf(CubemapUtils.CubemapFaces.class).forEach(face -> { 100 101 for (int i = 0; i < 4; i++) { 102 // Initialize four-tiled cubemap faces (four images per cube side with 18-length 103 // Quadkey) 104 if (!StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) { 105 StreetsideImage tile = new StreetsideImage(String.valueOf(image.getId() + Integer.valueOf(i))); 106 tiles.add(tile); 107 } 108 // Initialize four-tiled cubemap faces (four images per cub eside with 20-length 109 // Quadkey) 110 if (StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) { 111 for (int j = 0; j < 4; j++) { 112 StreetsideImage tile = new StreetsideImage( 113 String.valueOf( 114 image.getId() + face.getValue() + CubemapUtils.rowCol2StreetsideCellAddressMap 115 .get(String.valueOf(Integer.valueOf(i).toString() + Integer.valueOf(j).toString())) 116 )); 117 tiles.add(tile); 118 } 119 } 120 } 121 }); 122 123 bubbleImages.add(image); 124 logger.info("Added image with id <" + image.getId() + ">"); 125 if (StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get()) { 126 StreetsideData.downloadSurroundingCubemaps(image); 127 } 128 } 129 } 130 131 parser.close(); 132 133 } catch (JsonParseException e) { 134 e.printStackTrace(); 135 } catch (JsonMappingException e) { 136 e.printStackTrace(); 137 } catch (IOException e) { 138 e.printStackTrace(); 139 } 140 141 /** Top Level Bubble Metadata in Streetside are bubble (aka images) not Sequences 142 * so a sequence needs to be created and have images added to it. If the distribution 143 * of Streetside images is non-sequential, the Mapillary "Walking Action" may behave 144 * unpredictably. 145 **/ 146 seq.add(bubbleImages); 147 148 if (StreetsideProperties.CUT_OFF_SEQUENCES_AT_BOUNDS.get()) { 149 for (StreetsideAbstractImage img : seq.getImages()) { 150 if (bounds.contains(img.getLatLon())) { 151 data.add(img); 152 } else { 153 seq.remove(img); 154 } 155 } 156 } else { 157 boolean sequenceCrossesThroughBounds = false; 158 for (int i = 0; i < seq.getImages().size() && !sequenceCrossesThroughBounds; i++) { 159 sequenceCrossesThroughBounds = bounds.contains(seq.getImages().get(i).getLatLon()); 160 } 161 if (sequenceCrossesThroughBounds) { 162 data.addAll(seq.getImages(), true); 163 } 164 } 165 166 final long endTime = System.currentTimeMillis(); 167 logger.info("Sucessfully loaded " + seq.getImages().size() + " Microsoft Streetside images in " + (endTime-startTime/1000)); 168 } 169 170 @Override 171 protected Function<Bounds, URL> getUrlGenerator() { 172 return URL_GEN; 173 } 174}