    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}