Changeset 36228 in osm for applications/editors


Ignore:
Timestamp:
2024-03-14T16:34:12+01:00 (10 months ago)
Author:
taylor.smock
Message:

StreetSide: Update to official API

This also moves the plugin to Java 21 (mostly for virtual threads), reformats the
code to match the JOSM standard (4 spaces), and fixes a bunch of lint issues.

Additionally, a lot of cruft from when this plugin was copied from Mapillary was
removed. That was largely related to image import, uploading, and login.

Location:
applications/editors/josm/plugins
Files:
4 added
19 deleted
78 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/MicrosoftStreetside/.classpath

    r35601 r36228  
    2828                </attributes>
    2929        </classpathentry>
    30         <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
     30        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17/"/>
    3131        <classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
    3232        <classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
     
    3535        <classpathentry combineaccessrules="false" kind="src" path="/JOSM-apache-commons"/>
    3636        <classpathentry combineaccessrules="false" kind="src" path="/JOSM-apache-http"/>
    37         <classpathentry combineaccessrules="false" kind="src" path="/JOSM-utilsplugin2"/>
    3837        <classpathentry kind="output" path="bin/default"/>
    3938</classpath>
  • applications/editors/josm/plugins/MicrosoftStreetside/.gitignore

    r34432 r36228  
    99javadoc/
    1010build/
    11 bin/
    1211doc/
    1312logs/
     
    2120# OS X metadata
    2221.DS_Store
    23 
    24 
    25 /bin/
  • applications/editors/josm/plugins/MicrosoftStreetside/CONTRIBUTING.md

    r34352 r36228  
    1414
    1515The following format of source code files is preferred in this repository:
    16 * Indentation with 2 spaces per indentation level
     16* Indentation with 4 spaces per indentation level
    1717* line endings should be UNIX-style line endings (LF)
    1818* one newline (LF) at the end of the file
  • applications/editors/josm/plugins/MicrosoftStreetside/README.md

    r34428 r36228  
    2626    gradle build
    2727   
    28 Now Restart JOSM and activate the MicrosoftStreeside plugin in your preferences.
     28Now Restart JOSM and activate the MicrosoftStreetside plugin in your preferences.
    2929The MicrosoftStreetside menu items will appear in the JOSM main menu after JOSM is
    3030restarted.
     
    3434## License
    3535
    36 This plugin is based on the Mapilary developed by developed and maintained by nokutu (nokutu@openmailbox.org) and extended to display Streetside imagery by Rene Rhodes (renerr18) You can contact Rene on github.
     36This plugin is based on the Mapillary developed by developed and maintained by nokutu (nokutu@openmailbox.org) and extended to display Streetside imagery by Rene Rhodes (renerr18) You can contact Rene on GitHub.
    3737
    3838This software is licensed under [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html).
     
    4343Java SE 1.8 specification, and may not function properly with alternative JDKs (e.g. OpenJDK is not currently supported). JavaFX is licensed under the same terms as Java SE (http://www.oracle.com/technetwork/java/javase/terms/license/index.html).
    4444
    45 The plugin also makes use of the compact Resty libary for communicating with RESTful web services from Java (https://beders.github.io/Resty/Resty/Overview.html). Resty is licensed under the MIT license (https://github.com/go-resty/resty/blob/master/LICENSE).
     45Third-party JDKs such as Azul have versions with JavaFX included. Please use those.
  • applications/editors/josm/plugins/MicrosoftStreetside/build.gradle

    r36194 r36228  
     1import com.github.spotbugs.snom.Confidence
     2import com.github.spotbugs.snom.Effort
    13import com.github.spotbugs.snom.SpotBugsTask
    2 import net.ltgt.gradle.errorprone.CheckSeverity
     4import org.gradle.api.tasks.testing.logging.TestLogEvent
    35
    46plugins {
     
    79  id 'jacoco'
    810  id 'pmd'
    9   id("com.github.ben-manes.versions").version("0.49.0")
    10   id("net.ltgt.errorprone").version("3.1.0")
    11   id("org.kordamp.markdown.convert").version("1.2.0")
    12   id("org.sonarqube").version("3.3")
    13   id('com.github.spotbugs').version('5.2.3')
    14   id('org.openstreetmap.josm').version("0.8.2")
    15   id("com.diffplug.spotless").version("6.22.0")
     11  alias(libs.plugins.spotless)
     12  alias(libs.plugins.versions)
     13  alias(libs.plugins.errorprone)
     14  alias(libs.plugins.markdown)
     15  alias(libs.plugins.javafx)
     16  alias(libs.plugins.sonarqube)
     17  alias(libs.plugins.spotbugs)
     18  alias(libs.plugins.josm)
    1619}
    1720
     
    2124//apply from: 'gradle/markdown.gradle'
    2225
    23 sourceCompatibility = '1.8'
     26sourceCompatibility = '21'
    2427
    2528def versionProcess = new ProcessBuilder("git", "describe", "--always", "--dirty").start()
     
    4043}
    4144
    42 def versions = [
    43   awaitility: "4.2.0",
    44   jmockit: "1.49.a",
    45   junit: "5.10.1",
    46   wiremock: "2.27.2"
    47 ]
     45javafx {
     46    modules = [ 'javafx.graphics', 'javafx.controls', 'javafx.swing' ]
     47}
    4848
    4949dependencies {
    50   if (!JavaVersion.current().isJava9Compatible()) {
    51     errorproneJavac("com.google.errorprone:javac:9+181-r4173-1")
    52   }
    53   testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${versions.junit}")
    54   testImplementation("org.junit.jupiter:junit-jupiter-params:${versions.junit}")
    55   testImplementation("org.junit.jupiter:junit-jupiter-api:${versions.junit}")
    56   testImplementation("org.junit.vintage:junit-vintage-engine:${versions.junit}")
    57   testImplementation ("org.openstreetmap.josm:josm-unittest"){changing=true}
    58   testImplementation "com.github.tomakehurst:wiremock:${versions.wiremock}"
    59   testImplementation("org.jmockit:jmockit:${versions.jmockit}")
    60   testImplementation("org.awaitility:awaitility:${versions.awaitility}")
     50  errorprone(libs.errorprone)
     51  spotbugsPlugins(libs.findsecbugsPlugin)
     52  testRuntimeOnly libs.junit.jupiter.engine
     53  testImplementation libs.bundles.junit
     54  testImplementation libs.josm.unittest
     55  testImplementation libs.wiremock
     56  testImplementation libs.jmockit
     57  testImplementation libs.awaitility
    6158}
    6259
     
    8279    }
    8380  }
     81}
     82
     83// Spotbugs config
     84spotbugs {
     85    toolVersion = libs.versions.spotbugs.get()
     86    ignoreFailures = true
     87    effort = Effort.valueOf('MAX')
     88    reportLevel = Confidence.valueOf('LOW')
     89    //sourceSets = [sourceSets.main, sourceSets.test]
    8490}
    8591
     
    133139}
    134140
    135 import org.gradle.api.tasks.testing.logging.TestLogEvent
    136 
    137141test {
    138142  project.afterEvaluate {
  • applications/editors/josm/plugins/MicrosoftStreetside/build.xml

    r36194 r36228  
    1010  <property name="josm" location="../../core/dist/josm-custom.jar"/>
    1111  <property name="plugin.dist.dir" value="../../dist"/>
     12  <property name="java.lang.version" value="21"/>
    1213  <!--** include targets that all plugins have in common **-->
    1314  <import file="../build-common.xml"/>
    1415  <fileset id="plugin.requires.jars" dir="${plugin.dist.dir}">
    1516    <include name="apache-commons.jar"/>
    16     <include name="apache-http.jar"/>
    1717    <include name="javafx-osx.jar" if:set="isMac"/>
    1818    <include name="javafx-unixoid.jar" if:set="isUnix"/>
    1919    <include name="javafx-windows.jar" if:set="isWindows"/>
    20     <include name="utilsplugin2.jar"/>
    2120  </fileset>
    2221  <target name="pre-compile" depends="fetch_dependencies">
  • applications/editors/josm/plugins/MicrosoftStreetside/gradle.properties

    r36194 r36228  
    55plugin.icon=images/streetside-logo.svg
    66plugin.link=https://github.com/Microsoft/MicrosoftStreetsidePlugin
     7plugin.minimum.java.version=21
    78
    89# Minimum required JOSM version to run this plugin, choose the lowest version possible that is compatible.
    910# You can check if the plugin compiles against this version by executing `./gradlew compileJava_minJosm`.
    10 plugin.main.version=18723
     11plugin.main.version=18877
    1112#plugin.version=
    1213# Version of JOSM against which the plugin is compiled
    1314# Please check, if the specified version is available for download from https://josm.openstreetmap.de/download/ .
    1415# If not, choose the next higher number that is available, or the gradle build will break.
    15 plugin.compile.version=18724
    16 plugin.requires=apache-commons;apache-http;javafx;utilsplugin2
     16plugin.compile.version=18911
     17plugin.requires=apache-commons;javafx
    1718
    1819# Character encoding of Gradle files
  • applications/editors/josm/plugins/MicrosoftStreetside/gradle/tool-config.gradle

    r35779 r36228  
    1 def pmdVersion = "6.36.0"
    2 def spotbugsVersion = "4.3.0"
    3 def jacocoVersion = "0.8.7"
    4 def errorproneVersion = "2.7.1"
    5 
    6 // Set up ErrorProne (currently only for JDK8, until JDK9 is supported)
    7 dependencies.errorprone "com.google.errorprone:error_prone_core:$errorproneVersion"
    8 /*
    9 tasks.withType(JavaCompile) {
    10 options.compilerArgs += ['-Xep:DefaultCharset:ERROR',
    11   '-Xep:ClassCanBeStatic:ERROR',
    12   '-Xep:StringEquality:ERROR',
    13   '-Xep:MethodCanBeStatic:WARN',
    14   '-Xep:RemoveUnusedImports:WARN',
    15   '-Xep:PrivateConstructorForUtilityClass:WARN',
    16   '-Xep:WildcardImport:WARN',
    17   '-Xep:LambdaFunctionalInterface:WARN',
    18   '-Xep:ConstantField:WARN']
    19 }
    20 */
    21 
    22 // Spotbugs config
    23 spotbugs {
    24   toolVersion = spotbugsVersion
    25   ignoreFailures = true
    26   effort = "max"
    27   reportLevel = "low"
    28   //sourceSets = [sourceSets.main, sourceSets.test]
    29 }
    30 
    311// JaCoCo config
    322jacoco {
    33   toolVersion = jacocoVersion
     3  toolVersion = libs.versions.jacoco.get()
    344}
    355jacocoTestReport {
    366  reports {
    37     xml.enabled = true
    38     html.destination file("$buildDir/reports/jacoco")
     7    xml.required = true
    398  }
     9  dependsOn test
    4010}
    4111build.dependsOn jacocoTestReport
     
    4313// PMD config
    4414pmd {
    45   toolVersion pmdVersion
     15  toolVersion libs.versions.pmd.get()
    4616  ignoreFailures true
    4717  ruleSetConfig = resources.text.fromFile('config/pmd/ruleset.xml')
     
    5020
    5121// SonarQube config
    52 sonarqube {
     22sonar {
    5323  properties {
    5424    property 'sonar.forceAuthentication', 'true'
  • applications/editors/josm/plugins/MicrosoftStreetside/gradle/wrapper/gradle-wrapper.properties

    r36194 r36228  
    11distributionBase=GRADLE_USER_HOME
    22distributionPath=wrapper/dists
    3 distributionSha256Sum=740c2e472ee4326c33bf75a5c9f5cd1e69ecf3f9b580f6e236c86d1f3d98cfac
    4 distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip
     3distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
     4distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
     5networkTimeout=10000
     6validateDistributionUrl=true
    57zipStoreBase=GRADLE_USER_HOME
    68zipStorePath=wrapper/dists
  • applications/editors/josm/plugins/MicrosoftStreetside/gradlew

    r36194 r36228  
    1 #!/usr/bin/env sh
    2 
    3 #
    4 # Copyright 2015 the original author or authors.
     1#!/bin/sh
     2
     3#
     4# Copyright © 2015-2021 the original authors.
    55#
    66# Licensed under the Apache License, Version 2.0 (the "License");
     
    1818
    1919##############################################################################
    20 ##
    21 ##  Gradle start up script for UN*X
    22 ##
     20#
     21#   Gradle start up script for POSIX generated by Gradle.
     22#
     23#   Important for running:
     24#
     25#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
     26#       noncompliant, but you have some other compliant shell such as ksh or
     27#       bash, then to run this script, type that shell name before the whole
     28#       command line, like:
     29#
     30#           ksh Gradle
     31#
     32#       Busybox and similar reduced shells will NOT work, because this script
     33#       requires all of these POSIX shell features:
     34#         * functions;
     35#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
     36#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
     37#         * compound commands having a testable exit status, especially «case»;
     38#         * various built-in commands including «command», «set», and «ulimit».
     39#
     40#   Important for patching:
     41#
     42#   (2) This script targets any POSIX shell, so it avoids extensions provided
     43#       by Bash, Ksh, etc; in particular arrays are avoided.
     44#
     45#       The "traditional" practice of packing multiple parameters into a
     46#       space-separated string is a well documented source of bugs and security
     47#       problems, so this is (mostly) avoided, by progressively accumulating
     48#       options in "$@", and eventually passing that to Java.
     49#
     50#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
     51#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
     52#       see the in-line comments for details.
     53#
     54#       There are tweaks for specific operating systems such as AIX, CygWin,
     55#       Darwin, MinGW, and NonStop.
     56#
     57#   (3) This script is generated from the Groovy template
     58#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
     59#       within the Gradle project.
     60#
     61#       You can find Gradle at https://github.com/gradle/gradle/.
     62#
    2363##############################################################################
    2464
    2565# Attempt to set APP_HOME
     66
    2667# Resolve links: $0 may be a link
    27 PRG="$0"
    28 # Need this for relative symlinks.
    29 while [ -h "$PRG" ] ; do
    30     ls=`ls -ld "$PRG"`
    31     link=`expr "$ls" : '.*-> \(.*\)$'`
    32     if expr "$link" : '/.*' > /dev/null; then
    33         PRG="$link"
    34     else
    35         PRG=`dirname "$PRG"`"/$link"
    36     fi
     68app_path=$0
     69
     70# Need this for daisy-chained symlinks.
     71while
     72    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
     73    [ -h "$app_path" ]
     74do
     75    ls=$( ls -ld "$app_path" )
     76    link=${ls#*' -> '}
     77    case $link in             #(
     78      /*)   app_path=$link ;; #(
     79      *)    app_path=$APP_HOME$link ;;
     80    esac
    3781done
    38 SAVED="`pwd`"
    39 cd "`dirname \"$PRG\"`/" >/dev/null
    40 APP_HOME="`pwd -P`"
    41 cd "$SAVED" >/dev/null
    42 
    43 APP_NAME="Gradle"
    44 APP_BASE_NAME=`basename "$0"`
    45 
    46 # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
    47 DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
     82
     83# This is normally unused
     84# shellcheck disable=SC2034
     85APP_BASE_NAME=${0##*/}
     86# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
     87APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
    4888
    4989# Use the maximum available, or set MAX_FD != -1 to use that value.
    50 MAX_FD="maximum"
     90MAX_FD=maximum
    5191
    5292warn () {
    5393    echo "$*"
    54 }
     94} >&2
    5595
    5696die () {
     
    5999    echo
    60100    exit 1
    61 }
     101} >&2
    62102
    63103# OS specific support (must be 'true' or 'false').
     
    66106darwin=false
    67107nonstop=false
    68 case "`uname`" in
    69   CYGWIN* )
    70     cygwin=true
    71     ;;
    72   Darwin* )
    73     darwin=true
    74     ;;
    75   MSYS* | MINGW* )
    76     msys=true
    77     ;;
    78   NONSTOP* )
    79     nonstop=true
    80     ;;
     108case "$( uname )" in                #(
     109  CYGWIN* )         cygwin=true  ;; #(
     110  Darwin* )         darwin=true  ;; #(
     111  MSYS* | MINGW* )  msys=true    ;; #(
     112  NONSTOP* )        nonstop=true ;;
    81113esac
    82114
     
    88120    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
    89121        # IBM's JDK on AIX uses strange locations for the executables
    90         JAVACMD="$JAVA_HOME/jre/sh/java"
     122        JAVACMD=$JAVA_HOME/jre/sh/java
    91123    else
    92         JAVACMD="$JAVA_HOME/bin/java"
     124        JAVACMD=$JAVA_HOME/bin/java
    93125    fi
    94126    if [ ! -x "$JAVACMD" ] ; then
     
    99131    fi
    100132else
    101     JAVACMD="java"
    102     which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
     133    JAVACMD=java
     134    if ! command -v java >/dev/null 2>&1
     135    then
     136        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
    103137
    104138Please set the JAVA_HOME variable in your environment to match the
    105139location of your Java installation."
     140    fi
    106141fi
    107142
    108143# Increase the maximum file descriptors if we can.
    109 if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
    110     MAX_FD_LIMIT=`ulimit -H -n`
    111     if [ $? -eq 0 ] ; then
    112         if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
    113             MAX_FD="$MAX_FD_LIMIT"
     144if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
     145    case $MAX_FD in #(
     146      max*)
     147        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
     148        # shellcheck disable=SC2039,SC3045
     149        MAX_FD=$( ulimit -H -n ) ||
     150            warn "Could not query maximum file descriptor limit"
     151    esac
     152    case $MAX_FD in  #(
     153      '' | soft) :;; #(
     154      *)
     155        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
     156        # shellcheck disable=SC2039,SC3045
     157        ulimit -n "$MAX_FD" ||
     158            warn "Could not set maximum file descriptor limit to $MAX_FD"
     159    esac
     160fi
     161
     162# Collect all arguments for the java command, stacking in reverse order:
     163#   * args from the command line
     164#   * the main class name
     165#   * -classpath
     166#   * -D...appname settings
     167#   * --module-path (only if needed)
     168#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
     169
     170# For Cygwin or MSYS, switch paths to Windows format before running java
     171if "$cygwin" || "$msys" ; then
     172    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
     173    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
     174
     175    JAVACMD=$( cygpath --unix "$JAVACMD" )
     176
     177    # Now convert the arguments - kludge to limit ourselves to /bin/sh
     178    for arg do
     179        if
     180            case $arg in                                #(
     181              -*)   false ;;                            # don't mess with options #(
     182              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
     183                    [ -e "$t" ] ;;                      #(
     184              *)    false ;;
     185            esac
     186        then
     187            arg=$( cygpath --path --ignore --mixed "$arg" )
    114188        fi
    115         ulimit -n $MAX_FD
    116         if [ $? -ne 0 ] ; then
    117             warn "Could not set maximum file descriptor limit: $MAX_FD"
    118         fi
    119     else
    120         warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    121     fi
    122 fi
    123 
    124 # For Darwin, add options to specify how the application appears in the dock
    125 if $darwin; then
    126     GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
    127 fi
    128 
    129 # For Cygwin or MSYS, switch paths to Windows format before running java
    130 if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
    131     APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    132     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    133 
    134     JAVACMD=`cygpath --unix "$JAVACMD"`
    135 
    136     # We build the pattern for arguments to be converted via cygpath
    137     ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    138     SEP=""
    139     for dir in $ROOTDIRSRAW ; do
    140         ROOTDIRS="$ROOTDIRS$SEP$dir"
    141         SEP="|"
     189        # Roll the args list around exactly as many times as the number of
     190        # args, so each arg winds up back in the position where it started, but
     191        # possibly modified.
     192        #
     193        # NB: a `for` loop captures its iteration list before it begins, so
     194        # changing the positional parameters here affects neither the number of
     195        # iterations, nor the values presented in `arg`.
     196        shift                   # remove old arg
     197        set -- "$@" "$arg"      # push replacement arg
    142198    done
    143     OURCYGPATTERN="(^($ROOTDIRS))"
    144     # Add a user-defined pattern to the cygpath arguments
    145     if [ "$GRADLE_CYGPATTERN" != "" ] ; then
    146         OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    147     fi
    148     # Now convert the arguments - kludge to limit ourselves to /bin/sh
    149     i=0
    150     for arg in "$@" ; do
    151         CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
    152         CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
    153 
    154         if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
    155             eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
    156         else
    157             eval `echo args$i`="\"$arg\""
    158         fi
    159         i=`expr $i + 1`
    160     done
    161     case $i in
    162         0) set -- ;;
    163         1) set -- "$args0" ;;
    164         2) set -- "$args0" "$args1" ;;
    165         3) set -- "$args0" "$args1" "$args2" ;;
    166         4) set -- "$args0" "$args1" "$args2" "$args3" ;;
    167         5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
    168         6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
    169         7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
    170         8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
    171         9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    172     esac
    173 fi
    174 
    175 # Escape application args
    176 save () {
    177     for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
    178     echo " "
    179 }
    180 APP_ARGS=`save "$@"`
    181 
    182 # Collect all arguments for the java command, following the shell quoting and substitution rules
    183 eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
     199fi
     200
     201
     202# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
     203DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
     204
     205# Collect all arguments for the java command:
     206#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
     207#     and any embedded shellness will be escaped.
     208#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
     209#     treated as '${Hostname}' itself on the command line.
     210
     211set -- \
     212        "-Dorg.gradle.appname=$APP_BASE_NAME" \
     213        -classpath "$CLASSPATH" \
     214        org.gradle.wrapper.GradleWrapperMain \
     215        "$@"
     216
     217# Stop when "xargs" is not available.
     218if ! command -v xargs >/dev/null 2>&1
     219then
     220    die "xargs is not available"
     221fi
     222
     223# Use "xargs" to parse quoted args.
     224#
     225# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
     226#
     227# In Bash we could simply go:
     228#
     229#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
     230#   set -- "${ARGS[@]}" "$@"
     231#
     232# but POSIX shell has neither arrays nor command substitution, so instead we
     233# post-process each arg (as a line of input to sed) to backslash-escape any
     234# character that might be a shell metacharacter, then use eval to reverse
     235# that process (while maintaining the separation between arguments), and wrap
     236# the whole thing up as a single "set" statement.
     237#
     238# This will of course break if any of these variables contains a newline or
     239# an unmatched quote.
     240#
     241
     242eval "set -- $(
     243        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
     244        xargs -n1 |
     245        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
     246        tr '\n' ' '
     247    )" '"$@"'
    184248
    185249exec "$JAVACMD" "$@"
  • applications/editors/josm/plugins/MicrosoftStreetside/gradlew.bat

    r35779 r36228  
    1515@rem
    1616
    17 @if "%DEBUG%" == "" @echo off
     17@if "%DEBUG%"=="" @echo off
    1818@rem ##########################################################################
    1919@rem
     
    2626
    2727set DIRNAME=%~dp0
    28 if "%DIRNAME%" == "" set DIRNAME=.
     28if "%DIRNAME%"=="" set DIRNAME=.
     29@rem This is normally unused
    2930set APP_BASE_NAME=%~n0
    3031set APP_HOME=%DIRNAME%
     
    4142set JAVA_EXE=java.exe
    4243%JAVA_EXE% -version >NUL 2>&1
    43 if "%ERRORLEVEL%" == "0" goto execute
     44if %ERRORLEVEL% equ 0 goto execute
    4445
    45 echo.
    46 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
    47 echo.
    48 echo Please set the JAVA_HOME variable in your environment to match the
    49 echo location of your Java installation.
     46echo. 1>&2
     47echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
     48echo. 1>&2
     49echo Please set the JAVA_HOME variable in your environment to match the 1>&2
     50echo location of your Java installation. 1>&2
    5051
    5152goto fail
     
    5758if exist "%JAVA_EXE%" goto execute
    5859
    59 echo.
    60 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
    61 echo.
    62 echo Please set the JAVA_HOME variable in your environment to match the
    63 echo location of your Java installation.
     60echo. 1>&2
     61echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
     62echo. 1>&2
     63echo Please set the JAVA_HOME variable in your environment to match the 1>&2
     64echo location of your Java installation. 1>&2
    6465
    6566goto fail
     
    7677:end
    7778@rem End local scope for the variables with windows NT shell
    78 if "%ERRORLEVEL%"=="0" goto mainEnd
     79if %ERRORLEVEL% equ 0 goto mainEnd
    7980
    8081:fail
    8182rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
    8283rem the _cmd.exe /c_ return code!
    83 if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
    84 exit /b 1
     84set EXIT_CODE=%ERRORLEVEL%
     85if %EXIT_CODE% equ 0 set EXIT_CODE=1
     86if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
     87exit /b %EXIT_CODE%
    8588
    8689:mainEnd
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideAbstractImage.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside;
    33
    4 import org.openstreetmap.josm.data.coor.LatLon;
     4import java.io.Serializable;
     5import java.util.List;
     6import java.util.stream.IntStream;
     7import java.util.stream.Stream;
     8
     9import org.openstreetmap.gui.jmapviewer.TileXY;
     10import org.openstreetmap.josm.data.IQuadBucketType;
     11import org.openstreetmap.josm.data.coor.ILatLon;
     12import org.openstreetmap.josm.data.osm.BBox;
     13import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils;
     14import org.openstreetmap.josm.tools.Pair;
     15
     16import jakarta.annotation.Nonnull;
    517
    618/**
    7  * Abstract superclass for all image objects. At the moment there are 2,
    8  * {@link StreetsideImage}, {@link StreetsideCubemap}.
     19 * Abstract superclass for all image objects. At the moment there is one,
     20 * {@link StreetsideImage}.
    921 *
    1022 * @author nokutu
     
    1224 *
    1325 */
    14 public abstract class StreetsideAbstractImage implements Comparable<StreetsideAbstractImage> {
    15   /**
    16    * If two values for field cd differ by less than EPSILON both values are
    17    * considered equal.
    18    */
    19   private static final float EPSILON = 1e-5f;
     26public sealed
    2027
    21   protected String id;
    22   /**
    23    * Position of the picture.
    24    */
    25   protected LatLon latLon;
    26   //Image id of previous image in sequence (decimal)
    27   private long pr;
    28   /**
    29    * Direction of the picture in degrees from true north.
    30    */
    31   protected double he;
    32   /**
    33    * When the object direction is being moved in the map, the temporal direction
    34    * is stored here
    35    */
    36   protected double movingHe;
    37   // Image id of next image in sequence (decimal)
    38   private long ne;
    39   /**
    40    * Sequence of pictures containing this object.
    41    */
    42   private StreetsideSequence sequence;
    43   /**
    44    * Temporal position of the picture until it is uploaded.
    45    */
    46   private LatLon tempLatLon;
    47   /**
    48    * When the object is being dragged in the map, the temporal position is stored
    49    * here.
    50    */
    51   private LatLon movingLatLon;
    52   /**
    53    * Temporal direction of the picture until it is uploaded
    54    */
    55   private double tempHe;
    56   /**
    57    * Whether the image must be drown in the map or not
    58    */
    59   private boolean visible;
     28interface StreetsideAbstractImage extends ILatLon, IQuadBucketType, Comparable<StreetsideAbstractImage>, Serializable
     29permits StreetsideImage
     30{
    6031
    61   /**
    62    * Creates a new object in the given position and with the given direction.
    63    * {@link LatLon}
    64    *
    65    * @param id   - the Streetside image id
    66    * @param latLon The latitude and longitude of the image.
    67    * @param he   The direction of the picture (0 means north im Mapillary
    68    *         camera direction is not yet supported in the Streetside plugin).
    69    */
    70   protected StreetsideAbstractImage(final String id, final LatLon latLon, final double he) {
    71     this.id = id;
    72     this.latLon = latLon;
    73     tempLatLon = this.latLon;
    74     movingLatLon = this.latLon;
    75     this.he = he;
    76     tempHe = he;
    77     movingHe = he;
    78     visible = true;
    79   }
     32    /**
     33     * Get the ID for this image
     34     * @return the id
     35     */
     36    String id();
    8037
    81   /**
    82    * Creates a new object with the given id.
    83    *
    84    * @param id - the image id (All images require ids in Streetside)
    85    */
    86   protected StreetsideAbstractImage(final String id) {
    87     this.id = id;
     38    /**
     39     * Get the subdomains that can be used for this image
     40     * @return The valid subdomains
     41     */
     42    List<String> imageUrlSubdomains();
    8843
    89     visible = true;
    90   }
     44    /**
     45     * Returns the original direction towards the image has been taken.
     46     *
     47     * @return The direction of the image (0 means north and goes clockwise).
     48     */
     49    double heading();
    9150
    92   /**
    93    * @return the id
    94    */
    95   public String getId() {
    96     return id;
    97   }
     51    /**
     52     * The maximum zoom for this image
     53     * @return The max zoom
     54     */
     55    int zoomMax();
    9856
    99   /**
    100    * @param id the id to set
    101    */
    102   public void setId(String id) {
    103     this.id = id;
    104   }
     57    /**
     58     * The minimum zoom for this image
     59     * @return The min zoom
     60     */
     61    int zoomMin();
    10562
    106   /**
    107    * Returns the original direction towards the image has been taken.
    108    *
    109    * @return The direction of the image (0 means north and goes clockwise).
    110    */
    111   public double getHe() {
    112     return he;
    113   }
     63    /**
     64     * Get the number of x columns
     65     * @param zoom The zoom level
     66     * @return The columns for the x-axis
     67     */
     68    default int xCols(int zoom) {
     69        return yCols(zoom);
     70    }
    11471
    115   public void setHe(final double he) {
    116     this.he = he;
    117   }
     72    /**
     73     * Get the number of y columns
     74     * @param zoom The zoom level
     75     * @return The columns for the y-axis
     76     */
     77    default int yCols(int zoom) {
     78        return 1 << zoom;
     79    }
    11880
    119   /**
    120    * Returns a LatLon object containing the original coordinates of the object.
    121    *
    122    * @return The LatLon object with the position of the object.
    123    */
    124   public LatLon getLatLon() {
    125     return latLon;
    126   }
     81    /**
     82     * Check if the image is visible
     83     * @return {@code true} if the image is visible
     84     */
     85    default boolean visible() {
     86        return true;
     87    }
    12788
    128   public void setLatLon(final LatLon latLon) {
    129     if (latLon != null) {
    130       this.latLon = latLon;
     89    @Override
     90    default BBox getBBox() {
     91        return new BBox(this);
    13192    }
    132   }
    13393
    134   /**
    135    * Returns the direction towards the image has been taken.
    136    *
    137    * @return The direction of the image (0 means north and goes clockwise).
    138    */
    139   public double getMovingHe() {
    140     return movingHe;
    141   }
     94    /**
     95     * Get a thumbnail for the image
     96     * @return The URL for the thumbnail
     97     */
     98    default String getThumbnail() {
     99        // This is the "front" min zoom image
     100        return getTile(Integer.toString(0), Integer.toString(1));
     101    }
    142102
    143   /**
    144    * Returns a LatLon object containing the current coordinates of the object.
    145    * When you are dragging the image this changes.
    146    *
    147    * @return The LatLon object with the position of the object.
    148    */
    149   public LatLon getMovingLatLon() {
    150     return movingLatLon;
    151   }
     103    /**
     104     * Get the tiles for a face
     105     * @param face The id of the face
     106     * @param zoom The zoom level for the face
     107     * @return A stream of tile location + URL pairs
     108     */
     109    default Stream<Pair<CubeMapTileXY, String>> getFaceTiles(CubemapUtils.CubemapFaces face, int zoom) {
     110        if (zoom > this.zoomMax() || zoom < this.zoomMin()) {
     111            throw new IndexOutOfBoundsException(zoom);
     112        }
     113        final var faceId = face.faceId();
     114        final var startingTileId = face.startingTileId();
     115        // The {tileId} is chainable after the starting tile id
     116        // ---------------
     117        // | 0, 0 | 1, 0 |
     118        // | 0, 1 | 1, 1 |
     119        // ---------------
     120        // (0, 0) is 0
     121        // (1, 0) is 1
     122        // (0, 1) is 2
     123        // (1, 1) is 3
     124        // Zoom starts at 1
     125        int currentZoom = this.zoomMin();
     126        Stream<String> level = IntStream.range(0, 4).mapToObj(String::valueOf).map(startingTileId::concat);
     127        while (currentZoom < zoom) {
     128            level = level.flatMap(s -> IntStream.range(0, 4).mapToObj(String::valueOf).map(s::concat));
     129            currentZoom++;
     130        }
     131        return level.map(s -> new Pair<>(mapToTiles(face, zoom, s), getTile(faceId, s)));
     132    }
    152133
    153   /**
    154    * Returns the sequence which contains this image. Never null.
    155    *
    156    * @return The StreetsideSequence object that contains this StreetsideImage.
    157    */
     134    /**
     135     * Convert a tileId to a TileXY coordinate
     136     * @param face The cubemap face
     137     * @param zoom the zoom level
     138     * @param tileId The tile id (quadkey)
     139     * @return The xy coordinate
     140     */
     141    private static CubeMapTileXY mapToTiles(CubemapUtils.CubemapFaces face, int zoom, String tileId) {
     142        // Given a z level, the quadkey is the last z characters
     143        final var xy = quadKeyToTile(tileId.subSequence(tileId.length() - zoom, tileId.length()));
     144        return new CubeMapTileXY(face, xy.getXIndex(), xy.getYIndex());
     145    }
    158146
    159   public StreetsideSequence getSequence() {
    160     synchronized (this) {
    161       if (sequence == null) {
    162         sequence = new StreetsideSequence();
    163         sequence.add(this);
    164       }
    165       return sequence;
     147    /**
     148     * Convert a quadkey to a tile
     149     * @param quadkey The quadkey to convert
     150     * @return The tile for that quadkey
     151     */
     152    @Nonnull
     153    static TileXY quadKeyToTile(@Nonnull CharSequence quadkey) {
     154        final int z = quadkey.length();
     155        var x = 0;
     156        var y = 0;
     157        for (int i = z; i > 0; i--) {
     158            final var mask = 1 << (i - 1);
     159            switch (quadkey.charAt(z - i)) {
     160            case '0':
     161                break;
     162            case '1':
     163                x |= mask;
     164                break;
     165            case '2':
     166                y |= mask;
     167                break;
     168            case '3':
     169                x |= mask;
     170                y |= mask;
     171                break;
     172            default:
     173                throw new IllegalArgumentException("Bad quadtile character at " + (i - 1) + " for '" + quadkey + "'");
     174            }
     175        }
     176        return new TileXY(x, y);
    166177    }
    167   }
    168178
    169   /**
    170    * Sets the StreetsideSequence object which contains the StreetsideImage.
    171    *
    172    * @param sequence
    173    *      The StreetsideSequence that contains the StreetsideImage.
    174    * @throws IllegalArgumentException
    175    *       if the image is not already part of the
    176    *       {@link StreetsideSequence}. Call
    177    *       {@link StreetsideSequence#add(StreetsideAbstractImage)} first.
    178    */
    179   public void setSequence(final StreetsideSequence sequence) {
    180     synchronized (this) {
    181       if (sequence != null && !sequence.getImages().contains(this)) {
    182         throw new IllegalArgumentException();
    183       }
    184       this.sequence = sequence;
     179    /**
     180     * Get a tile for an image
     181     * @param faceId The face id
     182     * @param tileId The tile id
     183     * @return The URL for the face and tile
     184     * @see <a href="https://learn.microsoft.com/en-us/bingmaps/articles/getting-streetside-tiles-from-imagery-metadata">
     185     *     Getting Streetside Tiles from Imagery Metadata
     186     * </a>
     187     */
     188    default String getTile(String faceId, String tileId) {
     189        return this.id().replace("{subdomain}", this.imageUrlSubdomains().get(0)).replace("{faceId}", faceId)
     190                .replace("{tileId}", tileId);
    185191    }
    186   }
    187 
    188   /**
    189    * Returns the last fixed direction of the object.
    190    *
    191    * @return The last fixed direction of the object. 0 means north.
    192    */
    193   public double getTempHe() {
    194     return tempHe;
    195   }
    196 
    197   /**
    198    * Returns the last fixed coordinates of the object.
    199    *
    200    * @return A LatLon object containing.
    201    */
    202   public LatLon getTempLatLon() {
    203     return tempLatLon;
    204   }
    205 
    206   /**
    207    * Returns whether the object has been modified or not.
    208    *
    209    * @return true if the object has been modified; false otherwise.
    210    */
    211   public boolean isModified() {
    212     return !getMovingLatLon().equals(latLon) || Math.abs(getMovingHe() - he) > EPSILON;
    213   }
    214 
    215   /**
    216    * Returns whether the image is visible on the map or not.
    217    *
    218    * @return True if the image is visible; false otherwise.
    219    */
    220   public boolean isVisible() {
    221     return visible;
    222   }
    223 
    224   /**
    225    * Set's whether the image should be visible on the map or not.
    226    *
    227    * @param visible
    228    *      true if the image is set to be visible; false otherwise.
    229    */
    230   public void setVisible(final boolean visible) {
    231     this.visible = visible;
    232   }
    233 
    234   /**
    235    * Moves the image temporally to another position
    236    *
    237    * @param x The movement of the image in longitude units.
    238    * @param y The movement of the image in latitude units.
    239    */
    240   public void move(final double x, final double y) {
    241     movingLatLon = new LatLon(tempLatLon.getY() + y, tempLatLon.getX() + x);
    242   }
    243 
    244   /**
    245    * If the StreetsideImage belongs to a StreetsideSequence, returns the next
    246    * image in the sequence.
    247    *
    248    * @return The following StreetsideImage, or null if there is none.
    249    */
    250   public StreetsideAbstractImage next() {
    251     synchronized (this) {
    252       return getSequence().next(this);
    253     }
    254   }
    255 
    256   /**
    257    * If the StreetsideImage belongs to a StreetsideSequence, returns the previous
    258    * image in the sequence.
    259    *
    260    * @return The previous StreetsideImage, or null if there is none.
    261    */
    262   public StreetsideAbstractImage previous() {
    263     synchronized (this) {
    264       return getSequence().previous(this);
    265     }
    266   }
    267 
    268   /**
    269    * Called when the mouse button is released, meaning that the picture has
    270    * stopped being dragged, so the temporal values are saved.
    271    */
    272   public void stopMoving() {
    273     tempLatLon = movingLatLon;
    274     tempHe = movingHe;
    275   }
    276 
    277   /**
    278    * Turns the image direction.
    279    *
    280    * @param he
    281    *      The angle the image is moving.
    282    */
    283   public void turn(final double he) {
    284     movingHe = tempHe + he;
    285   }
    286 
    287   /**
    288    * @return the ne
    289    */
    290   public long getNe() {
    291     return ne;
    292   }
    293 
    294   /**
    295    * @param ne the ne to set
    296    */
    297   public void setNe(long ne) {
    298     this.ne = ne;
    299   }
    300 
    301   /**
    302    * @return the pr
    303    */
    304   public long getPr() {
    305     return pr;
    306   }
    307 
    308   /**
    309    * @param pr the pr to set
    310    */
    311   public void setPr(long pr) {
    312     this.pr = pr;
    313   }
    314 
    315192}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideData.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside;
    33
     4import java.util.ArrayList;
    45import java.util.Arrays;
    56import java.util.Collection;
    67import java.util.Collections;
     8import java.util.Comparator;
     9import java.util.HashSet;
    710import java.util.List;
    811import java.util.Objects;
    9 import java.util.Set;
    10 import java.util.concurrent.ConcurrentHashMap;
    1112import java.util.concurrent.CopyOnWriteArrayList;
    12 import java.util.stream.Collectors;
     13import java.util.function.Consumer;
    1314
    1415import org.apache.commons.jcs3.access.CacheAccess;
     
    1718import org.openstreetmap.josm.data.DataSource;
    1819import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
     20import org.openstreetmap.josm.data.coor.LatLon;
     21import org.openstreetmap.josm.data.osm.BBox;
     22import org.openstreetmap.josm.data.osm.QuadBuckets;
    1923import org.openstreetmap.josm.gui.MainApplication;
    20 import org.openstreetmap.josm.gui.MapView;
    2124import org.openstreetmap.josm.plugins.streetside.cache.CacheUtils;
    2225import org.openstreetmap.josm.plugins.streetside.cache.Caches;
     
    3235 * @author renerr18 (extended for Streetside)
    3336 * @see StreetsideAbstractImage
    34  * @see StreetsideSequence
    3537 */
    3638public class StreetsideData implements Data {
    37   private final Set<StreetsideAbstractImage> images = ConcurrentHashMap.newKeySet();
    38   /**
    39    * All the images selected, can be more than one.
    40    */
    41   private final Set<StreetsideAbstractImage> multiSelectedImages = ConcurrentHashMap.newKeySet();
    42   /**
    43    * Listeners of the class.
    44    */
    45   private final List<StreetsideDataListener> listeners = new CopyOnWriteArrayList<>();
    46   /**
    47    * The bounds of the areas for which the pictures have been downloaded.
    48    */
    49   private final List<Bounds> bounds;
    50   /**
    51    * The image currently selected, this is the one being shown.
    52    */
    53   private StreetsideAbstractImage selectedImage;
    54   /**
    55    * The image under the cursor.
    56    */
    57   private StreetsideAbstractImage highlightedImage;
    58 
    59   /**
    60    * Creates a new object and adds the initial set of listeners.
    61    */
    62   protected StreetsideData() {
    63     selectedImage = null;
    64     bounds = new CopyOnWriteArrayList<>();
    65 
    66     // Adds the basic set of listeners.
    67     Arrays.stream(StreetsidePlugin.getStreetsideDataListeners()).forEach(this::addListener);
    68     addListener(StreetsideViewerDialog.getInstance().getStreetsideViewerPanel());
    69     addListener(StreetsideMainDialog.getInstance());
    70     addListener(ImageInfoPanel.getInstance());
    71   }
    72 
    73   /**
    74    * Downloads surrounding images of this mapillary image in background threads
    75    *
    76    * @param streetsideImage the image for which the surrounding images should be downloaded
    77    */
    78   private static void downloadSurroundingImages(StreetsideImage streetsideImage) {
    79     MainApplication.worker.execute(() -> {
    80       final int prefetchCount = StreetsideProperties.PRE_FETCH_IMAGE_COUNT.get();
    81       CacheAccess<String, BufferedImageCacheEntry> imageCache = Caches.ImageCache.getInstance().getCache();
    82 
    83       StreetsideAbstractImage nextImage = streetsideImage.next();
    84       StreetsideAbstractImage prevImage = streetsideImage.previous();
    85 
    86       for (int i = 0; i < prefetchCount; i++) {
    87         if (nextImage != null) {
    88           if (nextImage instanceof StreetsideImage && imageCache.get(nextImage.getId()) == null) {
    89             CacheUtils.downloadPicture((StreetsideImage) nextImage);
    90           }
    91           nextImage = nextImage.next();
    92         }
    93         if (prevImage != null) {
    94           if (prevImage instanceof StreetsideImage && imageCache.get(prevImage.getId()) == null) {
    95             CacheUtils.downloadPicture((StreetsideImage) prevImage);
    96           }
    97           prevImage = prevImage.previous();
    98         }
    99       }
    100     });
    101   }
    102 
    103   /**
    104    * Downloads surrounding images of this mapillary image in background threads
    105    *
    106    * @param streetsideImage the image for which the surrounding images should be downloaded
    107    */
    108   public static void downloadSurroundingCubemaps(StreetsideImage streetsideImage) {
    109     MainApplication.worker.execute(() -> {
    110       final int prefetchCount = StreetsideProperties.PRE_FETCH_IMAGE_COUNT.get();
    111       CacheAccess<String, BufferedImageCacheEntry> imageCache = Caches.ImageCache.getInstance().getCache();
    112 
    113       StreetsideAbstractImage nextImage = streetsideImage.next();
    114       StreetsideAbstractImage prevImage = streetsideImage.previous();
    115 
    116       for (int i = 0; i < prefetchCount; i++) {
    117         if (nextImage != null) {
    118           if (nextImage instanceof StreetsideImage && imageCache.get(nextImage.getId()) == null) {
    119             CacheUtils.downloadCubemap((StreetsideImage) nextImage);
    120           }
    121           nextImage = nextImage.next();
    122         }
    123         if (prevImage != null) {
    124           if (prevImage instanceof StreetsideImage && imageCache.get(prevImage.getId()) == null) {
    125             CacheUtils.downloadCubemap((StreetsideImage) prevImage);
    126           }
    127           prevImage = prevImage.previous();
    128         }
    129       }
    130     });
    131   }
    132 
    133   /**
    134    * Adds an StreetsideImage to the object, and then repaints mapView.
    135    *
    136    * @param image The image to be added.
    137    */
    138   public void add(StreetsideAbstractImage image) {
    139     add(image, true);
    140   }
    141 
    142   /**
    143    * Adds a StreetsideImage to the object, but doesn't repaint mapView. This is
    144    * needed for concurrency.
    145    *
    146    * @param image  The image to be added.
    147    * @param update Whether the map must be updated or not
    148    *         (updates are currently unsupported by Streetside).
    149    */
    150   public void add(StreetsideAbstractImage image, boolean update) {
    151     images.add(image);
    152     if (update) {
    153       StreetsideLayer.invalidateInstance();
    154     }
    155     fireImagesAdded();
    156   }
    157 
    158   /**
    159    * Adds a set of StreetsideImages to the object, and then repaints mapView.
    160    *
    161    * @param images The set of images to be added.
    162    */
    163   public void addAll(Collection<? extends StreetsideAbstractImage> images) {
    164     addAll(images, true);
    165   }
    166 
    167   /**
    168    * Adds a set of {link StreetsideAbstractImage} objects to this object.
    169    *
    170    * @param newImages The set of images to be added.
    171    * @param update  Whether the map must be updated or not.
    172    */
    173   public void addAll(Collection<? extends StreetsideAbstractImage> newImages, boolean update) {
    174     images.addAll(newImages);
    175     if (update) {
    176       StreetsideLayer.invalidateInstance();
    177     }
    178     fireImagesAdded();
    179   }
    180 
    181   /**
    182    * Adds a new listener.
    183    *
    184    * @param lis Listener to be added.
    185    */
    186   public final void addListener(final StreetsideDataListener lis) {
    187     listeners.add(lis);
    188   }
    189 
    190   /**
    191    * Adds a {@link StreetsideImage} object to the list of selected images, (when
    192    * ctrl + click)
    193    *
    194    * @param image The {@link StreetsideImage} object to be added.
    195    */
    196   public void addMultiSelectedImage(final StreetsideAbstractImage image) {
    197     if (!multiSelectedImages.contains(image)) {
    198       if (getSelectedImage() == null) {
    199         this.setSelectedImage(image);
    200       } else {
    201         multiSelectedImages.add(image);
    202       }
    203     }
    204     StreetsideLayer.invalidateInstance();
    205   }
    206 
    207   /**
    208    * Adds a set of {@code StreetsideAbstractImage} objects to the list of
    209    * selected images.
    210    *
    211    * @param images A {@link Collection} object containing the set of images to be added.
    212    */
    213   public void addMultiSelectedImage(Collection<StreetsideAbstractImage> images) {
    214     images.stream().filter(image -> !multiSelectedImages.contains(image)).forEach(image -> {
    215       if (getSelectedImage() == null) {
    216         this.setSelectedImage(image);
    217       } else {
    218         multiSelectedImages.add(image);
    219       }
    220     });
    221     StreetsideLayer.invalidateInstance();
    222   }
    223 
    224   public List<Bounds> getBounds() {
    225     return bounds;
    226   }
    227 
    228   /**
    229    * Removes a listener.
    230    *
    231    * @param lis Listener to be removed.
    232    */
    233   public void removeListener(StreetsideDataListener lis) {
    234     listeners.remove(lis);
    235   }
    236 
    237   /**
    238    * Returns the image under the mouse cursor.
    239    *
    240    * @return The image under the mouse cursor.
    241    */
    242   public StreetsideAbstractImage getHighlightedImage() {
    243     return highlightedImage;
    244   }
    245 
    246   /**
    247    * Highlights the image under the cursor.
    248    *
    249    * @param image The image under the cursor.
    250    */
    251   public void setHighlightedImage(StreetsideAbstractImage image) {
    252     highlightedImage = image;
    253   }
    254 
    255   /**
    256    * Returns a Set containing all images.
    257    *
    258    * @return A Set object containing all images.
    259    */
    260   public Set<StreetsideAbstractImage> getImages() {
    261     return images;
    262   }
    263 
    264   /**
    265    * Sets a new {@link Collection} object as the used set of images.
    266    * Any images that are already present, are removed.
    267    *
    268    * @param newImages the new image list (previously set images are completely replaced)
    269    */
    270   public void setImages(Collection<StreetsideAbstractImage> newImages) {
    271     synchronized (this) {
    272       images.clear();
    273       images.addAll(newImages);
    274     }
    275   }
    276 
    277   /**
    278    * Returns a Set of all sequences, that the images are part of.
    279    *
    280    * @return all sequences that are contained in the Streetside data
    281    */
    282   public Set<StreetsideSequence> getSequences() {
    283     return images.stream().map(StreetsideAbstractImage::getSequence).collect(Collectors.toSet());
    284   }
    285 
    286   /**
    287    * Returns the StreetsideImage object that is currently selected.
    288    *
    289    * @return The selected StreetsideImage object.
    290    */
    291   public StreetsideAbstractImage getSelectedImage() {
    292     return selectedImage;
    293   }
    294 
    295   /**
    296    * Selects a new image.If the user does ctrl + click, this isn't triggered.
    297    *
    298    * @param image The StreetsideImage which is going to be selected
    299    */
    300   public void setSelectedImage(StreetsideAbstractImage image) {
    301     setSelectedImage(image, false);
    302   }
    303 
    304   private void fireImagesAdded() {
    305     listeners.stream().filter(Objects::nonNull).forEach(StreetsideDataListener::imagesAdded);
    306   }
    307 
    308   /**
    309    * If the selected StreetsideImage is part of a StreetsideSequence then the
    310    * following visible StreetsideImage is selected. In case there is none, does
    311    * nothing.
    312    *
    313    * @throws IllegalStateException if the selected image is null or the selected image doesn't
    314    *                 belong to a sequence.
    315    */
    316   public void selectNext() {
    317     selectNext(StreetsideProperties.MOVE_TO_IMG.get());
    318   }
    319 
    320   /**
    321    * If the selected StreetsideImage is part of a StreetsideSequence then the
    322    * following visible StreetsideImage is selected. In case there is none, does
    323    * nothing.
    324    *
    325    * @param moveToPicture True if the view must me moved to the next picture.
    326    * @throws IllegalStateException if the selected image is null or the selected image doesn't
    327    *                 belong to a sequence.
    328    */
    329   public void selectNext(boolean moveToPicture) {
    330     StreetsideAbstractImage tempImage = selectedImage;
    331     if (selectedImage != null && selectedImage.getSequence() != null) {
    332       while (tempImage.next() != null) {
    333         tempImage = tempImage.next();
    334         if (tempImage.isVisible()) {
    335           setSelectedImage(tempImage, moveToPicture);
    336           break;
    337         }
    338       }
    339     }
    340   }
    341 
    342   /**
    343    * If the selected StreetsideImage is part of a StreetsideSequence then the
    344    * previous visible StreetsideImage is selected. In case there is none, does
    345    * nothing.
    346    *
    347    * @throws IllegalStateException if the selected image is null or the selected image doesn't
    348    *                 belong to a sequence.
    349    */
    350   public void selectPrevious() {
    351     selectPrevious(StreetsideProperties.MOVE_TO_IMG.get());
    352   }
    353 
    354   /**
    355    * If the selected StreetsideImage is part of a StreetsideSequence then the
    356    * previous visible StreetsideImage is selected. In case there is none, does
    357    * nothing. * @throws IllegalStateException if the selected image is null or
    358    * the selected image doesn't belong to a sequence.
    359    *
    360    * @param moveToPicture True if the view must me moved to the previous picture.
    361    * @throws IllegalStateException if the selected image is null or the selected image doesn't
    362    *                 belong to a sequence.
    363    */
    364   public void selectPrevious(boolean moveToPicture) {
    365     if (selectedImage != null && selectedImage.getSequence() != null) {
    366       StreetsideAbstractImage tempImage = selectedImage;
    367       while (tempImage.previous() != null) {
    368         tempImage = tempImage.previous();
    369         if (tempImage.isVisible()) {
    370           setSelectedImage(tempImage, moveToPicture);
    371           break;
    372         }
    373       }
    374     }
    375   }
    376 
    377   /**
    378    * Selects a new image.If the user does ctrl+click, this isn't triggered. You
    379    * can choose whether to center the view on the new image or not.
    380    *
    381    * @param image The {@link StreetsideImage} which is going to be selected.
    382    * @param zoom  True if the view must be centered on the image; false otherwise.
    383    */
    384   public void setSelectedImage(StreetsideAbstractImage image, boolean zoom) {
    385     StreetsideAbstractImage oldImage = selectedImage;
    386     selectedImage = image;
    387     multiSelectedImages.clear();
    388     final MapView mv = StreetsidePlugin.getMapView();
    389     if (image != null) {
    390       multiSelectedImages.add(image);
    391       if (mv != null && image instanceof StreetsideImage) {
    392         StreetsideImage streetsideImage = (StreetsideImage) image;
    393 
    394         // Downloading thumbnails of surrounding pictures.
    395         downloadSurroundingImages(streetsideImage);
    396       }
    397     }
    398     if (mv != null && zoom && selectedImage != null) {
    399       mv.zoomTo(selectedImage.getMovingLatLon());
    400     }
    401     fireSelectedImageChanged(oldImage, selectedImage);
    402     StreetsideLayer.invalidateInstance();
    403   }
    404 
    405   private void fireSelectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) {
    406     listeners.stream().filter(Objects::nonNull).forEach(lis -> lis.selectedImageChanged(oldImage, newImage));
    407   }
    408 
    409   /**
    410    * Returns a List containing all {@code StreetsideAbstractImage} objects
    411    * selected with ctrl + click.
    412    *
    413    * @return A List object containing all the images selected.
    414    */
    415   public Set<StreetsideAbstractImage> getMultiSelectedImages() {
    416     return multiSelectedImages;
    417   }
    418 
    419   @Override
    420   public Collection<DataSource> getDataSources() {
    421     return Collections.emptyList();
    422   }
     39    private final QuadBuckets<StreetsideImage> images = new QuadBuckets<>();
     40    private final List<StreetsideImage> sortedImages = new ArrayList<>();
     41    /**
     42     * Listeners of the class.
     43     */
     44    private final List<StreetsideDataListener> listeners = new CopyOnWriteArrayList<>();
     45    /**
     46     * The bounds of the areas for which the pictures have been downloaded.
     47     */
     48    private final List<Bounds> bounds;
     49    /**
     50     * The image currently selected, this is the one being shown.
     51     */
     52    private StreetsideImage selectedImage;
     53    /**
     54     * The image under the cursor.
     55     */
     56    private StreetsideImage highlightedImage;
     57
     58    /**
     59     * Creates a new object and adds the initial set of listeners.
     60     */
     61    protected StreetsideData() {
     62        selectedImage = null;
     63        bounds = new CopyOnWriteArrayList<>();
     64
     65        // Adds the basic set of listeners.
     66        Arrays.stream(StreetsidePlugin.getStreetsideDataListeners()).forEach(this::addListener);
     67        addListener(StreetsideViewerDialog.getInstance().getStreetsideViewerPanel());
     68        addListener(StreetsideMainDialog.getInstance());
     69        addListener(ImageInfoPanel.getInstance());
     70    }
     71
     72    /**
     73     * Downloads surrounding images of this mapillary image in background threads
     74     *
     75     * @param streetsideImage the image for which the surrounding images should be downloaded
     76     */
     77    private void downloadSurroundingImages(StreetsideImage streetsideImage) {
     78        MainApplication.worker.execute(() -> downloadSurrounding(streetsideImage, CacheUtils::downloadPicture));
     79    }
     80
     81    /**
     82     * Downloads surrounding images of this mapillary image in background threads
     83     *
     84     * @param streetsideImage the image for which the surrounding images should be downloaded
     85     */
     86    public void downloadSurroundingCubemaps(StreetsideImage streetsideImage) {
     87        MainApplication.worker.execute(() -> downloadSurrounding(streetsideImage, CacheUtils::downloadCubemap));
     88    }
     89
     90    private void downloadSurrounding(StreetsideImage streetsideImage, Consumer<StreetsideImage> imageDownloader) {
     91        final int prefetchCount = StreetsideProperties.PRE_FETCH_IMAGE_COUNT.get();
     92        CacheAccess<String, BufferedImageCacheEntry> imageCache = Caches.ImageCache.getInstance().getCache();
     93
     94        StreetsideImage nextImage = next(streetsideImage);
     95        StreetsideImage prevImage = previous(streetsideImage);
     96
     97        for (var i = 0; i < prefetchCount; i++) {
     98            if (nextImage != null) {
     99                if (imageCache.get(nextImage.id()) == null) {
     100                    imageDownloader.accept(nextImage);
     101                }
     102                nextImage = next(nextImage);
     103            }
     104            if (prevImage != null) {
     105                if (imageCache.get(prevImage.id()) == null) {
     106                    imageDownloader.accept(prevImage);
     107                }
     108                prevImage = previous(prevImage);
     109            }
     110        }
     111    }
     112
     113    /**
     114     * Adds an StreetsideImage to the object, and then repaints mapView.
     115     *
     116     * @param image The image to be added.
     117     */
     118    public void add(StreetsideImage image) {
     119        add(image, true);
     120    }
     121
     122    /**
     123     * Adds a StreetsideImage to the object, but doesn't repaint mapView. This is
     124     * needed for concurrency.
     125     *
     126     * @param image  The image to be added.
     127     * @param update Whether the map must be updated or not
     128     *         (updates are currently unsupported by Streetside).
     129     */
     130    public void add(StreetsideImage image, boolean update) {
     131        if (this.images.contains(image)) {
     132            return;
     133        }
     134        this.images.add(image);
     135        if (update) {
     136            StreetsideLayer.invalidateInstance();
     137        }
     138        fireImagesAdded();
     139    }
     140
     141    /**
     142     * Adds a set of StreetsideImages to the object, and then repaints mapView.
     143     *
     144     * @param images The set of images to be added.
     145     */
     146    public void addAll(Collection<StreetsideImage> images) {
     147        addAll(images, true);
     148    }
     149
     150    /**
     151     * Adds a set of {link StreetsideAbstractImage} objects to this object.
     152     *
     153     * @param newImages The set of images to be added.
     154     * @param update  Whether the map must be updated or not.
     155     */
     156    public void addAll(Collection<StreetsideImage> newImages, boolean update) {
     157        newImages = new HashSet<>(newImages);
     158        newImages.removeIf(this.images::contains);
     159        images.addAll(newImages);
     160        sortedImages.addAll(newImages);
     161        sortedImages.sort(Comparator.naturalOrder());
     162        if (update) {
     163            StreetsideLayer.invalidateInstance();
     164        }
     165        fireImagesAdded();
     166    }
     167
     168    /**
     169     * Adds a new listener.
     170     *
     171     * @param lis Listener to be added.
     172     */
     173    public final void addListener(final StreetsideDataListener lis) {
     174        listeners.add(lis);
     175    }
     176
     177    public List<Bounds> getBounds() {
     178        return bounds;
     179    }
     180
     181    /**
     182     * Removes a listener.
     183     *
     184     * @param lis Listener to be removed.
     185     */
     186    public void removeListener(StreetsideDataListener lis) {
     187        listeners.remove(lis);
     188    }
     189
     190    /**
     191     * Returns the image under the mouse cursor.
     192     *
     193     * @return The image under the mouse cursor.
     194     */
     195    public StreetsideImage getHighlightedImage() {
     196        return highlightedImage;
     197    }
     198
     199    /**
     200     * Highlights the image under the cursor.
     201     *
     202     * @param image The image under the cursor.
     203     */
     204    public void setHighlightedImage(StreetsideImage image) {
     205        highlightedImage = image;
     206    }
     207
     208    /**
     209     * Returns a Set containing all images.
     210     *
     211     * @return A Set object containing all images.
     212     */
     213    public Collection<StreetsideImage> getImages() {
     214        return images;
     215    }
     216
     217    /**
     218     * Sets a new {@link Collection} object as the used set of images.
     219     * Any images that are already present, are removed.
     220     *
     221     * @param newImages the new image list (previously set images are completely replaced)
     222     */
     223    public void setImages(Collection<StreetsideImage> newImages) {
     224        synchronized (this) {
     225            this.images.clear();
     226            this.sortedImages.clear();
     227            this.addAll(newImages);
     228        }
     229    }
     230
     231    /**
     232     * Returns the StreetsideImage object that is currently selected.
     233     *
     234     * @return The selected StreetsideImage object.
     235     */
     236    public StreetsideImage getSelectedImage() {
     237        return selectedImage;
     238    }
     239
     240    /**
     241     * Selects a new image.If the user does ctrl + click, this isn't triggered.
     242     *
     243     * @param image The StreetsideImage which is going to be selected
     244     */
     245    public void setSelectedImage(StreetsideImage image) {
     246        setSelectedImage(image, false);
     247    }
     248
     249    private void fireImagesAdded() {
     250        listeners.stream().filter(Objects::nonNull).forEach(StreetsideDataListener::imagesAdded);
     251    }
     252
     253    /**
     254     * If the selected StreetsideImage is part of a StreetsideSequence then the
     255     * following visible StreetsideImage is selected. In case there is none, does
     256     * nothing.
     257     *
     258     * @throws IllegalStateException if the selected image is null or the selected image doesn't
     259     *                 belong to a sequence.
     260     */
     261    public void selectNext() {
     262        selectNext(StreetsideProperties.MOVE_TO_IMG.get());
     263    }
     264
     265    /**
     266     * If the selected StreetsideImage is part of a StreetsideSequence then the
     267     * following visible StreetsideImage is selected. In case there is none, does
     268     * nothing.
     269     *
     270     * @param moveToPicture True if the view must me moved to the next picture.
     271     * @throws IllegalStateException if the selected image is null or the selected image doesn't
     272     *                 belong to a sequence.
     273     */
     274    public void selectNext(boolean moveToPicture) {
     275        StreetsideImage tempImage = selectedImage;
     276        if (selectedImage != null) {
     277            while (next(tempImage) != null) {
     278                tempImage = next(tempImage);
     279                if (tempImage != null && tempImage.visible()) {
     280                    setSelectedImage(tempImage, moveToPicture);
     281                    break;
     282                }
     283            }
     284        }
     285    }
     286
     287    /**
     288     * If the selected StreetsideImage is part of a StreetsideSequence then the
     289     * previous visible StreetsideImage is selected. In case there is none, does
     290     * nothing.
     291     *
     292     * @throws IllegalStateException if the selected image is null or the selected image doesn't
     293     *                 belong to a sequence.
     294     */
     295    public void selectPrevious() {
     296        selectPrevious(StreetsideProperties.MOVE_TO_IMG.get());
     297    }
     298
     299    /**
     300     * If the selected StreetsideImage is part of a StreetsideSequence then the
     301     * previous visible StreetsideImage is selected. In case there is none, does
     302     * nothing. * @throws IllegalStateException if the selected image is null or
     303     * the selected image doesn't belong to a sequence.
     304     *
     305     * @param moveToPicture True if the view must me moved to the previous picture.
     306     * @throws IllegalStateException if the selected image is null or the selected image doesn't
     307     *                 belong to a sequence.
     308     */
     309    public void selectPrevious(boolean moveToPicture) {
     310        if (selectedImage != null) {
     311            StreetsideImage tempImage = selectedImage;
     312            while (previous(tempImage) != null) {
     313                tempImage = previous(tempImage);
     314                if (tempImage != null && tempImage.visible()) {
     315                    setSelectedImage(tempImage, moveToPicture);
     316                    break;
     317                }
     318            }
     319        }
     320    }
     321
     322    /**
     323     * Selects a new image.If the user does ctrl+click, this isn't triggered. You
     324     * can choose whether to center the view on the new image or not.
     325     *
     326     * @param image The {@link StreetsideImage} which is going to be selected.
     327     * @param zoom  True if the view must be centered on the image; false otherwise.
     328     */
     329    public void setSelectedImage(StreetsideImage image, boolean zoom) {
     330        StreetsideImage oldImage = selectedImage;
     331        selectedImage = image;
     332        final var mv = StreetsidePlugin.getMapView();
     333        if (image != null && mv != null) {
     334            // Downloading thumbnails of surrounding pictures.
     335            downloadSurroundingImages(image);
     336        }
     337        if (mv != null && zoom && selectedImage != null) {
     338            mv.zoomTo(selectedImage);
     339        }
     340        fireSelectedImageChanged(oldImage, selectedImage);
     341        StreetsideLayer.invalidateInstance();
     342    }
     343
     344    private void fireSelectedImageChanged(StreetsideImage oldImage, StreetsideImage newImage) {
     345        listeners.stream().filter(Objects::nonNull).forEach(lis -> lis.selectedImageChanged(oldImage, newImage));
     346    }
     347
     348    @Override
     349    public Collection<DataSource> getDataSources() {
     350        return Collections.emptyList();
     351    }
     352
     353    /**
     354     * Get the next image
     355     * @param current The current image
     356     * @return The next image, if available
     357     */
     358    public StreetsideImage next(StreetsideImage current) {
     359        final int currentIndex = sortedImages.indexOf(current);
     360        if (currentIndex + 1 >= sortedImages.size()) {
     361            return null;
     362        }
     363        return sortedImages.get(currentIndex + 1);
     364    }
     365
     366    /**
     367     * Get the previous image
     368     * @param current The current image
     369     * @return The previous image, if available
     370     */
     371    public StreetsideImage previous(StreetsideImage current) {
     372        final int currentIndex = sortedImages.indexOf(current);
     373        if (currentIndex - 1 < 0) {
     374            return null;
     375        }
     376        return sortedImages.get(currentIndex - 1);
     377    }
     378
     379    /**
     380     * Search for images
     381     * @param target The image to look around
     382     * @param v The {@link LatLon} degrees to search around
     383     * @return The images found
     384     */
     385    public Collection<StreetsideImage> search(StreetsideImage target, double v) {
     386        final var searchBox = new BBox(target);
     387        searchBox.addLatLon(new LatLon(target.lat(), target.lon()), v);
     388        return this.images.search(searchBox);
     389    }
     390
     391    /**
     392     * Search for images
     393     * @param searchBox The box to search images in
     394     * @return The images found
     395     */
     396    public Collection<StreetsideImage> search(BBox searchBox) {
     397        return this.images.search(searchBox);
     398    }
    423399}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideDataListener.java

    r36194 r36228  
    1010public interface StreetsideDataListener {
    1111
    12   /**
    13    * Fired when any image is added to the database.
    14    */
    15   void imagesAdded();
     12    /**
     13     * Fired when any image is added to the database.
     14     */
     15    void imagesAdded();
    1616
    17   /**
    18    * Fired when the selected image is changed by something different from
    19    * manually clicking on the icon.
    20    *
    21    * @param oldImage Old selected {@link StreetsideAbstractImage}
    22    * @param newImage New selected {@link StreetsideAbstractImage}
    23    */
    24   void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage);
     17    /**
     18     * Fired when the selected image is changed by something different from
     19     * manually clicking on the icon.
     20     *
     21     * @param oldImage Old selected {@link StreetsideImage}
     22     * @param newImage New selected {@link StreetsideImage}
     23     */
     24    void selectedImageChanged(StreetsideImage oldImage, StreetsideImage newImage);
    2525}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideImage.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside;
    33
     4import java.time.Instant;
    45import java.util.List;
    56
    6 import org.openstreetmap.josm.data.coor.LatLon;
    7 import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils;
    8 import org.openstreetmap.josm.plugins.streetside.model.UserProfile;
     7import jakarta.annotation.Nonnull;
    98
    109/**
    1110 * A StreetsideImage object represents each of the images stored in Streetside.
    1211 *
     12 * @param id The unique id for the cubemap
     13 * @param lat The latitude of the image
     14 * @param lon The longitude of the image
     15 * @param heading The direction of the images in degrees, meaning 0 north (not yet supported)
     16 * @param pitch The pitch of the image
     17 * @param roll The roll of the image
     18 * @param vintageEnd The date/time that the image was taken
     19 * @param vintageStart The date/time that the image was taken
     20 * @param logo The logo to show for this image
     21 * @param copyright The copyright to show for this image
     22 * @param zoomMax The maximum zoom for this image
     23 * @param zoomMin The minimum zoom for this image
     24 * @param imageHeight The height for this image tiles
     25 * @param imageWidth The width for this image tiles
     26 * @param imageUrlSubdomains The subdomains for this image
     27 *
    1328 * @author nokutu
    1429 * @author renerr18
    1530 *
    16  * @see StreetsideSequence
    1731 * @see StreetsideData
    1832 */
    19 public class StreetsideImage extends StreetsideAbstractImage {
    20   // latitude of the Streetside image
    21   private double la;
    22   //longitude of the Streetside image
    23   private double lo;
    24   // The bubble altitude, in meters above the WGS84 ellipsoid
    25   private double al;
    26   // Roll
    27   private double ro;
    28   // Pitch
    29   private double pi;
    30   // Blurring instructions - not currently used by the plugin
    31   private String bl;
    32   // Undocumented Attributes
    33   private int ml;
    34   private List<String> nbn;
    35   private List<String> pbn;
    36   private int ad;
    37   private Rn rn;
     33public record StreetsideImage(String id, double lat, double lon, double heading, double pitch, double roll,
     34                              Instant vintageStart, Instant vintageEnd, String logo, String copyright,
     35                              int zoomMin, int zoomMax, int imageHeight, int imageWidth,
     36                              List<String> imageUrlSubdomains) implements StreetsideAbstractImage {
     37    public StreetsideImage {
     38        if (lat > 90 || lat < -90) throw new IllegalArgumentException("Invalid latitude: " + lat);
     39        if (lon > 180 || lon < -180) throw new IllegalArgumentException("Invalid longitude: " + lon);
     40        if (pitch > 360 || pitch < -360) throw new IllegalArgumentException("Invalid pitch: " + pitch); // Is this radians or degrees?
     41        if (roll > 360 || roll < -360) throw new IllegalArgumentException("Invalid roll: " + roll); // Is this radians or degrees?
     42    }
    3843
    39   /**
    40    * Main constructor of the class StreetsideImage
    41    *
    42    * @param id   The unique identifier of the image.
    43    * @param latLon The latitude and longitude where it is positioned.
    44    * @param he   The direction of the images in degrees, meaning 0 north.
    45    */
    46   public StreetsideImage(String id, LatLon latLon, double he) {
    47     super(id, latLon, he);
    48   }
    49 
    50   public StreetsideImage(String id, double la, double lo) {
    51     super(id, new LatLon(la, lo), 0.0);
    52   }
    53 
    54   public StreetsideImage(String id) {
    55     super(id);
    56   }
    57 
    58   // Default constructor for Jackson/JSON Deserializattion
    59   public StreetsideImage() {
    60     super(CubemapUtils.TEST_IMAGE_ID, null, 0.0);
    61   }
    62 
    63   /**
    64    * Returns the unique identifier of the object.
    65    *
    66    * @return A {@code String} containing the unique identifier of the object.
    67    */
    68   @Override
    69   public String getId() {
    70     return String.valueOf(id);
    71   }
    72 
    73   /**
    74    * @param id the id to set
    75    */
    76   @Override
    77   public void setId(String id) {
    78     this.id = id;
    79   }
    80 
    81   public UserProfile getUser() {
    82     return getSequence().getUser();
    83   }
    84 
    85   @Override
    86   public String toString() {
    87     return String.format("Image[id=%s,lat=%f,lon=%f,he=%f,user=%s]", id, latLon.lat(), latLon.lon(), he, "null"//, cd
    88     );
    89   }
    90 
    91   @Override
    92   public boolean equals(Object object) {
    93     return object instanceof StreetsideImage && id.equals(((StreetsideImage) object).getId());
    94   }
    95 
    96   @Override
    97   public int compareTo(StreetsideAbstractImage image) {
    98     if (image instanceof StreetsideImage) {
    99       return id.compareTo(image.getId());
     44    @Override
     45    public int compareTo(@Nonnull StreetsideAbstractImage o) {
     46        if (o instanceof StreetsideImage other) {
     47            if (this.vintageStart.compareTo(other.vintageStart) != 0) {
     48                return this.vintageStart.compareTo(other.vintageStart);
     49            }
     50            if (this.vintageEnd.compareTo(other.vintageEnd) != 0) {
     51                return this.vintageEnd.compareTo(other.vintageEnd);
     52            }
     53            if (this.id.compareTo(o.id()) != 0) {
     54                return this.id.compareTo(o.id());
     55            }
     56            // Fine. Fall back to all the doubles.
     57            return (int) Math.round((this.lat + this.lon + this.heading + this.pitch + this.roll) -
     58                    (other.lat + other.lon + other.heading + other.pitch + other.roll));
     59        }
     60        return this.hashCode() - o.hashCode();
    10061    }
    101     return hashCode() - image.hashCode();
    102   }
    103 
    104   @Override
    105   public int hashCode() {
    106     return id.hashCode();
    107   }
    108 
    109   @Override
    110   public void stopMoving() {
    111     super.stopMoving();
    112     checkModified();
    113   }
    114 
    115   private void checkModified() {
    116     // modifications not currently supported in Streetside
    117   }
    118 
    119   @Override
    120   public void turn(double ca) {
    121     super.turn(ca);
    122     checkModified();
    123   }
    124 
    125   /**
    126    * @return the altitude
    127    */
    128   public double getAl() {
    129     return al;
    130   }
    131 
    132   /**
    133    * @param altitude the altitude to set
    134    */
    135   public void setAl(double altitude) {
    136     al = altitude;
    137   }
    138 
    139   /**
    140    * @return the roll
    141    */
    142   public double getRo() {
    143     return ro;
    144   }
    145 
    146   /**
    147    * @param roll the roll to set
    148    */
    149   public void setRo(double roll) {
    150     ro = roll;
    151   }
    152 
    153   /**
    154    * @return the pi
    155    */
    156   public double getPi() {
    157     return pi;
    158   }
    159 
    160   /**
    161    * @param pitch the pi to set
    162    */
    163   public void setPi(double pitch) {
    164     pi = pitch;
    165   }
    166 
    167   /**
    168    * @return the burringl
    169    */
    170   public String getBl() {
    171     return bl;
    172   }
    173 
    174   /**
    175    * @param blurring the blurring to set
    176    */
    177   public void setBl(String blurring) {
    178     bl = blurring;
    179   }
    180 
    181   /**
    182    * @return the ml
    183    */
    184   public int getMl() {
    185     return ml;
    186   }
    187 
    188   /**
    189    * @param ml the ml to set
    190    */
    191   public void setMl(int ml) {
    192     this.ml = ml;
    193   }
    194 
    195   /**
    196    * @return the nbn
    197    */
    198   public List<String> getNbn() {
    199     return nbn;
    200   }
    201 
    202   /**
    203    * @param nbn the nbn to set
    204    */
    205   public void setNbn(List<String> nbn) {
    206     this.nbn = nbn;
    207   }
    208 
    209   /**
    210    * @return the pbn
    211    */
    212   public List<String> getPbn() {
    213     return pbn;
    214   }
    215 
    216   /**
    217    * @param pbn the pbn to set
    218    */
    219   public void setPbn(List<String> pbn) {
    220     this.pbn = pbn;
    221   }
    222 
    223   /**
    224    * @return the ad
    225    */
    226   public int getAd() {
    227     return ad;
    228   }
    229 
    230   /**
    231    * @param ad the ad to set
    232    */
    233   public void setAd(int ad) {
    234     this.ad = ad;
    235   }
    236 
    237   /**
    238    * @return the la
    239    */
    240   public double getLa() {
    241     return la;
    242   }
    243 
    244   /**
    245    * @param la the la to set
    246    */
    247   public void setLa(double la) {
    248     this.la = la;
    249   }
    250 
    251   /**
    252    * @return the lo
    253    */
    254   public double getLo() {
    255     return lo;
    256   }
    257 
    258   /**
    259    * @param lo the lo to set
    260    */
    261   public void setLo(double lo) {
    262     this.lo = lo;
    263   }
    264 
    265   /**
    266    * @return the rn
    267    */
    268   public Rn getRn() {
    269     return rn;
    270   }
    271 
    272   /**
    273    * @param rn the rn to set
    274    */
    275   public void setRn(Rn rn) {
    276     this.rn = rn;
    277   }
    278 
    279   /**
    280    * Rn is a Bing Streetside image attribute - currently not
    281    * used, mapped or supported in the Streetside plugin -
    282    * left out initially because it's an unrequired complex object.
    283    */
    284   public static class Rn {
    285     // placeholder for Rn attribute (undocumented streetside complex inner type)
    286   }
    28762}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideLayer.java

    r36194 r36228  
    55import java.awt.BasicStroke;
    66import java.awt.Color;
    7 import java.awt.Composite;
    87import java.awt.Graphics2D;
    98import java.awt.GraphicsEnvironment;
    10 import java.awt.Point;
    119import java.awt.Rectangle;
    1210import java.awt.RenderingHints;
     
    1513import java.awt.image.BufferedImage;
    1614import java.util.Comparator;
    17 import java.util.IntSummaryStatistics;
    18 import java.util.Optional;
     15import java.util.Objects;
    1916import java.util.logging.Logger;
    2017
     
    2320
    2421import org.openstreetmap.josm.data.Bounds;
    25 import org.openstreetmap.josm.data.coor.ILatLon;
    26 import org.openstreetmap.josm.data.osm.DataSet;
    2722import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
    2823import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
     
    3934import org.openstreetmap.josm.plugins.streetside.cache.CacheUtils;
    4035import org.openstreetmap.josm.plugins.streetside.gui.StreetsideMainDialog;
    41 import org.openstreetmap.josm.plugins.streetside.history.StreetsideRecord;
    4236import org.openstreetmap.josm.plugins.streetside.io.download.StreetsideDownloader;
    4337import org.openstreetmap.josm.plugins.streetside.io.download.StreetsideDownloader.DOWNLOAD_MODE;
     
    5953 */
    6054public final class StreetsideLayer extends AbstractModifiableLayer
    61     implements ActiveLayerChangeListener, StreetsideDataListener {
    62 
    63   private static final Logger LOGGER = Logger.getLogger(StreetsideLayer.class.getCanonicalName());
    64 
    65   /**
    66    * The radius of the image marker
    67    */
    68   private static final int IMG_MARKER_RADIUS = 7;
    69   /**
    70    * The radius of the circular sector that indicates the camera angle
    71    */
    72   private static final int CA_INDICATOR_RADIUS = 15;
    73   /**
    74    * The angle of the circular sector that indicates the camera angle
    75    */
    76   private static final int CA_INDICATOR_ANGLE = 40;
    77   /**
    78    * Unique instance of the class.
    79    */
    80   private static StreetsideLayer instance;
    81   private static final DataSetListenerAdapter DATASET_LISTENER = new DataSetListenerAdapter(e -> {
    82     if (e instanceof DataChangedEvent && StreetsideDownloader.getMode() == DOWNLOAD_MODE.OSM_AREA) {
    83       // When more data is downloaded, a delayed update is thrown, in order to
    84       // wait for the data bounds to be set.
    85       MainApplication.worker.execute(StreetsideDownloader::downloadOSMArea);
    86     }
    87   });
    88   /**
    89    * {@link StreetsideData} object that stores the database.
    90    */
    91   private final StreetsideData data;
    92   /**
    93    * Mode of the layer.
    94    */
    95   public AbstractMode mode;
    96   /**
    97    * The nearest images to the selected image from different sequences sorted by distance from selection.
    98    */
    99   private StreetsideImage[] nearestImages = {};
    100   private volatile TexturePaint hatched;
    101 
    102   private StreetsideLayer() {
    103     super(I18n.tr("Microsoft Streetside Images"));
    104     data = new StreetsideData();
    105     data.addListener(this);
    106   }
    107 
    108   public static void invalidateInstance() {
    109     if (hasInstance()) {
    110       getInstance().invalidate();
    111     }
    112   }
    113 
    114   private static synchronized void clearInstance() {
    115     instance = null;
    116   }
    117 
    118   /**
    119    * Returns the unique instance of this class.
    120    *
    121    * @return The unique instance of this class.
    122    */
    123   public static synchronized StreetsideLayer getInstance() {
    124     if (instance != null) {
    125       return instance;
    126     }
    127     final StreetsideLayer layer = new StreetsideLayer();
    128     layer.init();
    129     instance = layer; // Only set instance field after initialization is complete
    130     return instance;
    131   }
    132 
    133   /**
    134    * @return if the unique instance of this layer is currently instantiated
    135    */
    136   public static boolean hasInstance() {
    137     return instance != null;
    138   }
    139 
    140   /**
    141    * Initializes the Layer.
    142    */
    143   private void init() {
    144     final DataSet ds = MainApplication.getLayerManager().getEditDataSet();
    145     if (ds != null) {
    146       ds.addDataSetListener(DATASET_LISTENER);
    147     }
    148     MainApplication.getLayerManager().addActiveLayerChangeListener(this);
    149     if (!GraphicsEnvironment.isHeadless()) {
    150       setMode(new SelectMode());
    151       if (StreetsideDownloader.getMode() == DOWNLOAD_MODE.OSM_AREA) {
    152         MainApplication.worker.execute(StreetsideDownloader::downloadOSMArea);
    153       }
    154       if (StreetsideDownloader.getMode() == DOWNLOAD_MODE.VISIBLE_AREA) {
    155         mode.zoomChanged();
    156       }
    157     }
    158     // Does not execute when in headless mode
    159     if (!StreetsideMainDialog.getInstance().isShowing()) {
    160       StreetsideMainDialog.getInstance().showDialog();
    161     }
    162     if (StreetsidePlugin.getMapView() != null) {
    163       StreetsideMainDialog.getInstance().streetsideImageDisplay.repaint();
    164     }
    165     createHatchTexture();
    166     invalidate();
    167   }
    168 
    169   /**
    170    * Changes the mode the the given one.
    171    *
    172    * @param mode The mode that is going to be activated.
    173    */
    174   public void setMode(AbstractMode mode) {
    175     final MapView mv = StreetsidePlugin.getMapView();
    176     if (this.mode != null && mv != null) {
    177       mv.removeMouseListener(this.mode);
    178       mv.removeMouseMotionListener(this.mode);
    179       NavigatableComponent.removeZoomChangeListener(this.mode);
    180     }
    181     this.mode = mode;
    182     if (mode != null && mv != null) {
    183       mv.setNewCursor(mode.cursor, this);
    184       mv.addMouseListener(mode);
    185       mv.addMouseMotionListener(mode);
    186       NavigatableComponent.addZoomChangeListener(mode);
    187       StreetsideUtils.updateHelpText();
    188     }
    189   }
    190 
    191   /**
    192    * Returns the {@link StreetsideData} object, which acts as the database of the
    193    * Layer.
    194    *
    195    * @return The {@link StreetsideData} object that stores the database.
    196    */
    197   @Override
    198   public StreetsideData getData() {
    199     return data;
    200   }
    201 
    202   /**
    203    * Returns the n-nearest image, for n=1 the nearest one is returned, for n=2 the second nearest one and so on.
    204    * The "n-nearest image" is picked from the list of one image from every sequence that is nearest to the currently
    205    * selected image, excluding the sequence to which the selected image belongs.
    206    *
    207    * @param n the index for picking from the list of "nearest images", beginning from 1
    208    * @return the n-nearest image to the currently selected image
    209    */
    210   public synchronized StreetsideImage getNNearestImage(final int n) {
    211     return n >= 1 && n <= nearestImages.length ? nearestImages[n - 1] : null;
    212   }
    213 
    214   @Override
    215   public synchronized void destroy() {
    216     clearInstance();
    217     setMode(null);
    218     StreetsideRecord.getInstance().reset();
    219     AbstractMode.resetThread();
    220     StreetsideDownloader.stopAll();
    221     if (StreetsideMainDialog.hasInstance()) {
    222       StreetsideMainDialog.getInstance().setImage(null);
    223       StreetsideMainDialog.getInstance().updateImage();
    224     }
    225     final MapView mv = StreetsidePlugin.getMapView();
    226     if (mv != null) {
    227       mv.removeMouseListener(mode);
    228       mv.removeMouseMotionListener(mode);
    229     }
    230     try {
    231       MainApplication.getLayerManager().removeActiveLayerChangeListener(this);
    232       if (MainApplication.getLayerManager().getEditDataSet() != null) {
    233         MainApplication.getLayerManager().getEditDataSet().removeDataSetListener(DATASET_LISTENER);
    234       }
    235     } catch (IllegalArgumentException e) {
    236       Logging.trace(e);
    237       // TODO: It would be ideal, to fix this properly. But for the moment let's catch this, for when a listener has already been removed.
    238     }
    239     super.destroy();
    240   }
    241 
    242   @Override
    243   public boolean isModified() {
    244     return data.getImages().parallelStream().anyMatch(StreetsideAbstractImage::isModified);
    245   }
    246 
    247   @Override
    248   public void setVisible(boolean visible) {
    249     super.setVisible(visible);
    250     getData().getImages().parallelStream().forEach(img -> img.setVisible(visible));
    251   }
    252 
    253   /**
    254    * Initialize the hatch pattern used to paint the non-downloaded area.
    255    */
    256   private void createHatchTexture() {
    257     BufferedImage bi = new BufferedImage(15, 15, BufferedImage.TYPE_INT_ARGB);
    258     Graphics2D big = bi.createGraphics();
    259     big.setColor(StreetsideProperties.BACKGROUND.get());
    260     Composite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);
    261     big.setComposite(comp);
    262     big.fillRect(0, 0, 15, 15);
    263     big.setColor(StreetsideProperties.OUTSIDE_DOWNLOADED_AREA.get());
    264     big.drawLine(0, 15, 15, 0);
    265     Rectangle r = new Rectangle(0, 0, 15, 15);
    266     hatched = new TexturePaint(bi, r);
    267   }
    268 
    269   @Override
    270   public synchronized void paint(final Graphics2D g, final MapView mv, final Bounds box) {
    271     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    272     if (MainApplication.getLayerManager().getActiveLayer() == this) {
    273       // paint remainder
    274       g.setPaint(hatched);
    275       g.fill(MapViewGeometryUtil.getNonDownloadedArea(mv, data.getBounds()));
    276     }
    277 
    278     // Draw the blue and red line
    279     synchronized (StreetsideLayer.class) {
    280       final StreetsideAbstractImage selectedImg = data.getSelectedImage();
    281       for (int i = 0; i < nearestImages.length && selectedImg != null; i++) {
    282         if (i == 0) {
    283           g.setColor(Color.RED);
     55        implements ActiveLayerChangeListener, StreetsideDataListener {
     56
     57    private static final Logger LOGGER = Logger.getLogger(StreetsideLayer.class.getCanonicalName());
     58
     59    /**
     60     * The radius of the image marker
     61     */
     62    private static final int IMG_MARKER_RADIUS = 7;
     63    /**
     64     * The radius of the circular sector that indicates the camera angle
     65     */
     66    private static final int CA_INDICATOR_RADIUS = 15;
     67    /**
     68     * The angle of the circular sector that indicates the camera angle
     69     */
     70    private static final int CA_INDICATOR_ANGLE = 40;
     71    /**
     72     * Unique instance of the class.
     73     */
     74    private static StreetsideLayer instance;
     75    private static final DataSetListenerAdapter DATASET_LISTENER = new DataSetListenerAdapter(e -> {
     76        if (e instanceof DataChangedEvent && StreetsideDownloader.getMode() == DOWNLOAD_MODE.OSM_AREA) {
     77            // When more data is downloaded, a delayed update is thrown, in order to
     78            // wait for the data bounds to be set.
     79            MainApplication.worker.execute(StreetsideDownloader::downloadOSMArea);
     80        }
     81    });
     82    /**
     83     * {@link StreetsideData} object that stores the database.
     84     */
     85    private final StreetsideData data;
     86    /**
     87     * Mode of the layer.
     88     */
     89    public AbstractMode mode;
     90    /**
     91     * The nearest images to the selected image from different sequences sorted by distance from selection.
     92     */
     93    private StreetsideImage[] nearestImages = {};
     94    private volatile TexturePaint hatched;
     95
     96    private StreetsideLayer() {
     97        super(I18n.tr("Microsoft Streetside Images"));
     98        data = new StreetsideData();
     99        data.addListener(this);
     100    }
     101
     102    public static void invalidateInstance() {
     103        if (hasInstance()) {
     104            getInstance().invalidate();
     105        }
     106    }
     107
     108    private static synchronized void clearInstance() {
     109        instance = null;
     110    }
     111
     112    /**
     113     * Returns the unique instance of this class.
     114     *
     115     * @return The unique instance of this class.
     116     */
     117    public static synchronized StreetsideLayer getInstance() {
     118        if (instance != null) {
     119            return instance;
     120        }
     121        final var layer = new StreetsideLayer();
     122        layer.init();
     123        instance = layer; // Only set instance field after initialization is complete
     124        return instance;
     125    }
     126
     127    /**
     128     * Check if there is a Microsoft Streetside layer
     129     * @return if the unique instance of this layer is currently instantiated
     130     */
     131    public static boolean hasInstance() {
     132        return instance != null;
     133    }
     134
     135    /**
     136     * Initializes the Layer.
     137     */
     138    private void init() {
     139        final var ds = MainApplication.getLayerManager().getEditDataSet();
     140        if (ds != null) {
     141            ds.addDataSetListener(DATASET_LISTENER);
     142        }
     143        MainApplication.getLayerManager().addActiveLayerChangeListener(this);
     144        if (!GraphicsEnvironment.isHeadless()) {
     145            setMode(new SelectMode());
     146            if (StreetsideDownloader.getMode() == DOWNLOAD_MODE.OSM_AREA) {
     147                MainApplication.worker.execute(StreetsideDownloader::downloadOSMArea);
     148            }
     149            if (StreetsideDownloader.getMode() == DOWNLOAD_MODE.VISIBLE_AREA) {
     150                mode.zoomChanged();
     151            }
     152        }
     153        // Does not execute when in headless mode
     154        if (!StreetsideMainDialog.getInstance().isShowing()) {
     155            StreetsideMainDialog.getInstance().showDialog();
     156        }
     157        if (StreetsidePlugin.getMapView() != null) {
     158            StreetsideMainDialog.getInstance().streetsideImageDisplay.repaint();
     159        }
     160        createHatchTexture();
     161        invalidate();
     162    }
     163
     164    /**
     165     * Changes the mode the given one.
     166     *
     167     * @param mode The mode that is going to be activated.
     168     */
     169    public void setMode(AbstractMode mode) {
     170        final var mv = StreetsidePlugin.getMapView();
     171        if (this.mode != null && mv != null) {
     172            mv.removeMouseListener(this.mode);
     173            mv.removeMouseMotionListener(this.mode);
     174            NavigatableComponent.removeZoomChangeListener(this.mode);
     175        }
     176        this.mode = mode;
     177        if (mode != null && mv != null) {
     178            mv.setNewCursor(mode.cursor, this);
     179            mv.addMouseListener(mode);
     180            mv.addMouseMotionListener(mode);
     181            NavigatableComponent.addZoomChangeListener(mode);
     182            StreetsideUtils.updateHelpText();
     183        }
     184    }
     185
     186    @Override
     187    public boolean isModified() {
     188        return false;
     189    }
     190
     191    /**
     192     * Returns the {@link StreetsideData} object, which acts as the database of the
     193     * Layer.
     194     *
     195     * @return The {@link StreetsideData} object that stores the database.
     196     */
     197    @Override
     198    public StreetsideData getData() {
     199        return data;
     200    }
     201
     202    /**
     203     * Returns the n-nearest image, for n=1 the nearest one is returned, for n=2 the second nearest one and so on.
     204     * The "n-nearest image" is picked from the list of one image from every sequence that is nearest to the currently
     205     * selected image, excluding the sequence to which the selected image belongs.
     206     *
     207     * @param n the index for picking from the list of "nearest images", beginning from 1
     208     * @return the n-nearest image to the currently selected image
     209     */
     210    public synchronized StreetsideImage getNNearestImage(final int n) {
     211        return n >= 1 && n <= nearestImages.length ? nearestImages[n - 1] : null;
     212    }
     213
     214    @Override
     215    public synchronized void destroy() {
     216        clearInstance();
     217        setMode(null);
     218        AbstractMode.resetThread();
     219        StreetsideDownloader.stopAll();
     220        if (StreetsideMainDialog.hasInstance()) {
     221            StreetsideMainDialog.getInstance().setImage(null);
     222            StreetsideMainDialog.getInstance().updateImage();
     223        }
     224        final var mv = StreetsidePlugin.getMapView();
     225        if (mv != null) {
     226            mv.removeMouseListener(mode);
     227            mv.removeMouseMotionListener(mode);
     228        }
     229        try {
     230            MainApplication.getLayerManager().removeActiveLayerChangeListener(this);
     231            if (MainApplication.getLayerManager().getEditDataSet() != null) {
     232                MainApplication.getLayerManager().getEditDataSet().removeDataSetListener(DATASET_LISTENER);
     233            }
     234        } catch (IllegalArgumentException e) {
     235            Logging.trace(e);
     236            // TODO: It would be ideal, to fix this properly. But for the moment let's catch this, for when a listener has already been removed.
     237        }
     238        super.destroy();
     239    }
     240
     241    /**
     242     * Initialize the hatch pattern used to paint the non-downloaded area.
     243     */
     244    private void createHatchTexture() {
     245        final var bufferedImage = new BufferedImage(15, 15, BufferedImage.TYPE_INT_ARGB);
     246        final var g2d = bufferedImage.createGraphics();
     247        g2d.setColor(StreetsideProperties.BACKGROUND.get());
     248        final var composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);
     249        g2d.setComposite(composite);
     250        g2d.fillRect(0, 0, 15, 15);
     251        g2d.setColor(StreetsideProperties.OUTSIDE_DOWNLOADED_AREA.get());
     252        g2d.drawLine(0, 15, 15, 0);
     253        final var rectangle = new Rectangle(0, 0, 15, 15);
     254        hatched = new TexturePaint(bufferedImage, rectangle);
     255    }
     256
     257    @Override
     258    public synchronized void paint(final Graphics2D g, final MapView mv, final Bounds box) {
     259        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
     260        if (MainApplication.getLayerManager().getActiveLayer() == this) {
     261            // paint remainder
     262            g.setPaint(hatched);
     263            g.fill(MapViewGeometryUtil.getNonDownloadedArea(mv, data.getBounds()));
     264        }
     265
     266        // Draw the blue and red line
     267        synchronized (StreetsideLayer.class) {
     268            final var selectedImg = data.getSelectedImage();
     269            for (var i = 0; i < nearestImages.length && selectedImg != null; i++) {
     270                if (i == 0) {
     271                    g.setColor(Color.RED);
     272                } else {
     273                    g.setColor(Color.BLUE);
     274                }
     275                final var selected = mv.getPoint(selectedImg);
     276                final var point = mv.getPoint(nearestImages[i]);
     277                g.draw(new Line2D.Double(point.getX(), point.getY(), selected.getX(), selected.getY()));
     278            }
     279        }
     280
     281        for (var imageAbs : data.getImages()) {
     282            if (imageAbs.visible() && mv != null && mv.contains(mv.getPoint(imageAbs))) {
     283                drawImageMarker(g, imageAbs);
     284            }
     285        }
     286    }
     287
     288    /**
     289     * Draws an image marker onto the given Graphics context.
     290     *
     291     * @param g   the Graphics context
     292     * @param img the image to be drawn onto the Graphics context
     293     */
     294    private void drawImageMarker(final Graphics2D g, final StreetsideAbstractImage img) {
     295        if (img == null || Double.isNaN(img.lat()) || Double.isNaN(img.lon())) {
     296            LOGGER.warning("An image is not painted, because it is null or has no LatLon!");
     297            return;
     298        }
     299        final var selectedImg = getData().getSelectedImage();
     300        final var point = MainApplication.getMap().mapView.getPoint(img);
     301
     302        // Determine colors
     303        final Color markerC;
     304        final Color directionC;
     305        if (img.equals(selectedImg)) {
     306            markerC = StreetsideColorScheme.SEQ_HIGHLIGHTED;
     307            directionC = StreetsideColorScheme.SEQ_HIGHLIGHTED_CA;
    284308        } else {
    285           g.setColor(Color.BLUE);
    286         }
    287         final Point selected = mv.getPoint(selectedImg.getMovingLatLon());
    288         final Point p = mv.getPoint(nearestImages[i].getMovingLatLon());
    289         g.draw(new Line2D.Double(p.getX(), p.getY(), selected.getX(), selected.getY()));
    290       }
    291     }
    292 
    293     // TODO: Sequence lines removed because Streetside imagery is organized
    294     // such that the images are sorted by the distance from the center of
    295     // the bounding box - Redefine sequences?
    296 
    297     // Draw sequence line
    298     /*g.setStroke(new BasicStroke(2));
    299     final StreetsideAbstractImage selectedImage = getData().getSelectedImage();
    300     for (StreetsideSequence seq : getData().getSequences()) {
    301       if (seq.getImages().contains(selectedImage)) {
    302     g.setColor(
    303       seq.getId() == null ? StreetsideColorScheme.SEQ_IMPORTED_SELECTED : StreetsideColorScheme.SEQ_SELECTED
    304     );
    305       } else {
    306     g.setColor(
    307       seq.getId() == null ? StreetsideColorScheme.SEQ_IMPORTED_UNSELECTED : StreetsideColorScheme.SEQ_UNSELECTED
    308     );
    309       }
    310       g.draw(MapViewGeometryUtil.getSequencePath(mv, seq));
    311     }*/
    312     for (StreetsideAbstractImage imageAbs : data.getImages()) {
    313       if (imageAbs.isVisible() && mv != null && mv.contains(mv.getPoint(imageAbs.getMovingLatLon()))) {
    314         drawImageMarker(g, imageAbs);
    315       }
    316     }
    317   }
    318 
    319   /**
    320    * Draws an image marker onto the given Graphics context.
    321    *
    322    * @param g   the Graphics context
    323    * @param img the image to be drawn onto the Graphics context
    324    */
    325   private void drawImageMarker(final Graphics2D g, final StreetsideAbstractImage img) {
    326     if (img == null || img.getLatLon() == null) {
    327       LOGGER.warning("An image is not painted, because it is null or has no LatLon!");
    328       return;
    329     }
    330     final StreetsideAbstractImage selectedImg = getData().getSelectedImage();
    331     final Point p = MainApplication.getMap().mapView.getPoint(img.getMovingLatLon());
    332 
    333     // Determine colors
    334     final Color markerC;
    335     final Color directionC;
    336     if (selectedImg != null && getData().getMultiSelectedImages().contains(img)) {
    337       markerC = StreetsideColorScheme.SEQ_HIGHLIGHTED;
    338       directionC = StreetsideColorScheme.SEQ_HIGHLIGHTED_CA;
    339     } else if (selectedImg != null && selectedImg.getSequence() != null
    340         && selectedImg.getSequence().equals(img.getSequence())) {
    341       markerC = StreetsideColorScheme.SEQ_SELECTED;
    342       directionC = StreetsideColorScheme.SEQ_SELECTED_CA;
    343     } else {
    344       markerC = StreetsideColorScheme.SEQ_UNSELECTED;
    345       directionC = StreetsideColorScheme.SEQ_UNSELECTED_CA;
    346     }
    347 
    348     // Paint direction indicator
    349     float alpha = 0.75f;
    350     int type = AlphaComposite.SRC_OVER;
    351     AlphaComposite composite = AlphaComposite.getInstance(type, alpha);
    352     g.setComposite(composite);
    353     g.setColor(directionC);
    354     g.fillArc(p.x - CA_INDICATOR_RADIUS, p.y - CA_INDICATOR_RADIUS, 2 * CA_INDICATOR_RADIUS,
    355         2 * CA_INDICATOR_RADIUS, (int) (90 - /*img.getMovingHe()*/img.getHe() - CA_INDICATOR_ANGLE / 2d),
    356         CA_INDICATOR_ANGLE);
    357     // Paint image marker
    358     g.setColor(markerC);
    359     g.fillOval(p.x - IMG_MARKER_RADIUS, p.y - IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS);
    360 
    361     // Paint highlight for selected or highlighted images
    362     if (img.equals(getData().getHighlightedImage()) || getData().getMultiSelectedImages().contains(img)) {
    363       g.setColor(Color.WHITE);
    364       g.setStroke(new BasicStroke(2));
    365       g.drawOval(p.x - IMG_MARKER_RADIUS, p.y - IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS);
    366     }
    367   }
    368 
    369   @Override
    370   public Icon getIcon() {
    371     return StreetsidePlugin.LOGO.setSize(ImageSizes.LAYER).get();
    372   }
    373 
    374   @Override
    375   public boolean isMergable(Layer other) {
    376     return false;
    377   }
    378 
    379   @Override
    380   public void mergeFrom(Layer from) {
    381     throw new UnsupportedOperationException("This layer does not support merging yet");
    382   }
    383 
    384   @Override
    385   public Action[] getMenuEntries() {
    386     return new Action[] { LayerListDialog.getInstance().createShowHideLayerAction(),
    387         LayerListDialog.getInstance().createDeleteLayerAction(), new LayerListPopup.InfoAction(this) };
    388   }
    389 
    390   @Override
    391   public Object getInfoComponent() {
    392     IntSummaryStatistics seqSizeStats = getData().getSequences().stream().mapToInt(seq -> seq.getImages().size())
    393         .summaryStatistics();
    394     return I18n.tr("Streetside layer") + '\n'
    395         + I18n.tr("{0} sequences, each containing between {1} and {2} images (ø {3})",
    396             getData().getSequences().size(), seqSizeStats.getCount() <= 0 ? 0 : seqSizeStats.getMin(),
    397             seqSizeStats.getCount() <= 0 ? 0 : seqSizeStats.getMax(), seqSizeStats.getAverage())
    398         + "\n\n" + "\n+ "
    399         + I18n.tr("{0} downloaded images",
    400             getData().getImages().stream().filter(i -> i instanceof StreetsideImage).count())
    401         + "\n= " + I18n.tr("{0} images in total", getData().getImages().size());
    402   }
    403 
    404   @Override
    405   public String getToolTipText() {
    406     return I18n.tr("{0} images in {1} sequences", getData().getImages().size(), getData().getSequences().size());
    407   }
    408 
    409   @Override
    410   public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
    411     if (MainApplication.getLayerManager().getActiveLayer() == this) {
    412       StreetsideUtils.updateHelpText();
    413     }
    414 
    415     if (MainApplication.getLayerManager().getEditLayer() != e.getPreviousDataLayer()) {
    416       if (MainApplication.getLayerManager().getEditLayer() != null) {
    417         MainApplication.getLayerManager().getEditLayer().getDataSet().addDataSetListener(DATASET_LISTENER);
    418       }
    419       if (e.getPreviousDataLayer() != null) {
    420         e.getPreviousDataLayer().getDataSet().removeDataSetListener(DATASET_LISTENER);
    421       }
    422     }
    423   }
    424 
    425   @Override
    426   public void visitBoundingBox(BoundingXYVisitor v) {
    427   }
    428 
    429   /* (non-Javadoc)
    430    * @see org.openstreetmap.josm.plugins.streetside.StreetsideDataListener#imagesAdded()
    431    */
    432   @Override
    433   public void imagesAdded() {
    434     updateNearestImages();
    435   }
    436 
    437   /* (non-Javadoc)
    438    * @see org.openstreetmap.josm.plugins.streetside.StreetsideDataListener#selectedImageChanged(org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage, org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage)
    439    */
    440   @Override
    441   public void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) {
    442     updateNearestImages();
    443   }
    444 
    445   /**
    446    * Returns the closest images belonging to a different sequence and
    447    * different from the specified target image.
    448    *
    449    * @param target the image for which you want to find the nearest other images
    450    * @param limit  the maximum length of the returned array
    451    * @return An array containing the closest images belonging to different sequences sorted by distance from target.
    452    */
    453   private StreetsideImage[] getNearestImagesFromDifferentSequences(StreetsideAbstractImage target, int limit) {
    454     return data.getSequences().parallelStream()
    455         .filter(seq -> seq.getId() != null && !seq.getId().equals(target.getSequence().getId())).map(seq -> { // Maps sequence to image from sequence that is nearest to target
    456           Optional<StreetsideAbstractImage> resImg = seq.getImages().parallelStream()
    457               .filter(img -> img instanceof StreetsideImage && img.isVisible())
    458               .min(new NearestImgToTargetComparator(target));
    459           return resImg.orElse(null);
    460         }).filter(img -> // Filters out images too far away from target
    461         img != null && img.getMovingLatLon().greatCircleDistance(
    462             (ILatLon) target.getMovingLatLon()) < StreetsideProperties.SEQUENCE_MAX_JUMP_DISTANCE.get())
    463         .sorted(new NearestImgToTargetComparator(target)).limit(limit).toArray(StreetsideImage[]::new);
    464   }
    465 
    466   private synchronized void updateNearestImages() {
    467     final StreetsideAbstractImage selected = data.getSelectedImage();
    468     if (selected != null) {
    469       nearestImages = getNearestImagesFromDifferentSequences(selected, 2);
    470     } else {
    471       nearestImages = new StreetsideImage[0];
    472     }
    473     if (MainApplication.isDisplayingMapView()) {
    474       StreetsideMainDialog.getInstance().redButton.setEnabled(nearestImages.length >= 1);
    475       StreetsideMainDialog.getInstance().blueButton.setEnabled(nearestImages.length >= 2);
    476     }
    477     if (nearestImages.length >= 1) {
    478       CacheUtils.downloadPicture(nearestImages[0]);
    479       if (nearestImages.length >= 2) {
    480         CacheUtils.downloadPicture(nearestImages[1]);
    481       }
    482     }
    483   }
    484 
    485   private static class NearestImgToTargetComparator implements Comparator<StreetsideAbstractImage> {
    486     private final StreetsideAbstractImage target;
    487 
    488     public NearestImgToTargetComparator(StreetsideAbstractImage target) {
    489       this.target = target;
     309            markerC = StreetsideColorScheme.SEQ_UNSELECTED;
     310            directionC = StreetsideColorScheme.SEQ_UNSELECTED_CA;
     311        }
     312
     313        // Paint direction indicator
     314        final var alpha = 0.75f;
     315        final int type = AlphaComposite.SRC_OVER;
     316        final var composite = AlphaComposite.getInstance(type, alpha);
     317        g.setComposite(composite);
     318        g.setColor(directionC);
     319        g.fillArc(point.x - CA_INDICATOR_RADIUS, point.y - CA_INDICATOR_RADIUS, 2 * CA_INDICATOR_RADIUS,
     320                2 * CA_INDICATOR_RADIUS, (int) (90 - /*img.getMovingHe()*/img.heading() - CA_INDICATOR_ANGLE / 2d),
     321                CA_INDICATOR_ANGLE);
     322        // Paint image marker
     323        g.setColor(markerC);
     324        g.fillOval(point.x - IMG_MARKER_RADIUS, point.y - IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS);
     325
     326        // Paint highlight for selected or highlighted images
     327        if (img.equals(getData().getHighlightedImage())) {
     328            g.setColor(Color.WHITE);
     329            g.setStroke(new BasicStroke(2));
     330            g.drawOval(point.x - IMG_MARKER_RADIUS, point.y - IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS);
     331        }
     332    }
     333
     334    @Override
     335    public Icon getIcon() {
     336        return StreetsidePlugin.LOGO.setSize(ImageSizes.LAYER).get();
     337    }
     338
     339    @Override
     340    public boolean isMergable(Layer other) {
     341        return false;
     342    }
     343
     344    @Override
     345    public void mergeFrom(Layer from) {
     346        throw new UnsupportedOperationException("This layer does not support merging yet");
     347    }
     348
     349    @Override
     350    public Action[] getMenuEntries() {
     351        return new Action[] { LayerListDialog.getInstance().createShowHideLayerAction(),
     352                LayerListDialog.getInstance().createDeleteLayerAction(), new LayerListPopup.InfoAction(this) };
     353    }
     354
     355    @Override
     356    public Object getInfoComponent() {
     357        return I18n.tr("Streetside layer") + '\n'
     358                + I18n.tr("{0} downloaded images", getData().getImages().stream().filter(Objects::nonNull).count())
     359                + "\n= " + I18n.tr("{0} images in total", getData().getImages().size());
     360    }
     361
     362    @Override
     363    public String getToolTipText() {
     364        return I18n.tr("{0} images", getData().getImages().size());
     365    }
     366
     367    @Override
     368    public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
     369        if (MainApplication.getLayerManager().getActiveLayer() == this) {
     370            StreetsideUtils.updateHelpText();
     371        }
     372
     373        if (MainApplication.getLayerManager().getEditLayer() != e.getPreviousDataLayer()) {
     374            if (MainApplication.getLayerManager().getEditLayer() != null) {
     375                MainApplication.getLayerManager().getEditLayer().getDataSet().addDataSetListener(DATASET_LISTENER);
     376            }
     377            if (e.getPreviousDataLayer() != null) {
     378                e.getPreviousDataLayer().getDataSet().removeDataSetListener(DATASET_LISTENER);
     379            }
     380        }
     381    }
     382
     383    @Override
     384    public void visitBoundingBox(BoundingXYVisitor v) {
     385        // Streetside currently doesn't care about this
    490386    }
    491387
    492388    /* (non-Javadoc)
    493      * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
    494      */
    495     @Override
    496     public int compare(StreetsideAbstractImage img1, StreetsideAbstractImage img2) {
    497       return (int) Math.signum(img1.getMovingLatLon().greatCircleDistance((ILatLon) target.getMovingLatLon())
    498           - img2.getMovingLatLon().greatCircleDistance((ILatLon) target.getMovingLatLon()));
    499     }
    500   }
    501 
     389     * @see org.openstreetmap.josm.plugins.streetside.StreetsideDataListener#imagesAdded()
     390     */
     391    @Override
     392    public void imagesAdded() {
     393        updateNearestImages();
     394    }
     395
     396    /* (non-Javadoc)
     397     * @see org.openstreetmap.josm.plugins.streetside.StreetsideDataListener#selectedImageChanged(
     398     *      org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage,
     399     *      org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage)
     400     */
     401    @Override
     402    public void selectedImageChanged(StreetsideImage oldImage, StreetsideImage newImage) {
     403        updateNearestImages();
     404    }
     405
     406    /**
     407     * Returns the closest images belonging to a different sequence and
     408     * different from the specified target image.
     409     *
     410     * @param target the image for which you want to find the nearest other images
     411     * @param limit  the maximum length of the returned array
     412     * @return An array containing the closest images belonging to different sequences sorted by distance from target.
     413     */
     414    private StreetsideImage[] getNearestImagesFromDifferentSequences(StreetsideImage target, int limit) {
     415        return data.search(target, 0.01).parallelStream().filter(i -> !target.equals(i))
     416                // Filters out images too far away from target
     417                .filter(img -> img != null
     418                        && img.greatCircleDistance(target) < StreetsideProperties.SEQUENCE_MAX_JUMP_DISTANCE.get())
     419                .sorted(new NearestImgToTargetComparator(target)).map(StreetsideImage.class::cast).limit(limit)
     420                .toArray(StreetsideImage[]::new);
     421    }
     422
     423    private synchronized void updateNearestImages() {
     424        final StreetsideImage selected = data.getSelectedImage();
     425        if (selected != null) {
     426            nearestImages = getNearestImagesFromDifferentSequences(selected, 2);
     427        } else {
     428            nearestImages = new StreetsideImage[0];
     429        }
     430        if (MainApplication.isDisplayingMapView()) {
     431            StreetsideMainDialog.getInstance().redButton.setEnabled(nearestImages.length >= 1);
     432            StreetsideMainDialog.getInstance().blueButton.setEnabled(nearestImages.length >= 2);
     433        }
     434        if (nearestImages.length >= 1) {
     435            CacheUtils.downloadPicture(nearestImages[0]);
     436            if (nearestImages.length >= 2) {
     437                CacheUtils.downloadPicture(nearestImages[1]);
     438            }
     439        }
     440    }
     441
     442    private record NearestImgToTargetComparator(StreetsideAbstractImage target) implements Comparator<StreetsideAbstractImage> {
     443        @Override
     444        public int compare(StreetsideAbstractImage img1, StreetsideAbstractImage img2) {
     445            return (int) Math.signum(img1.greatCircleDistance(target) - img2.greatCircleDistance(target));
     446        }
     447    }
    502448}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsidePlugin.java

    r36194 r36228  
    1111import org.openstreetmap.josm.plugins.streetside.actions.StreetsideDownloadAction;
    1212import org.openstreetmap.josm.plugins.streetside.actions.StreetsideDownloadViewAction;
    13 import org.openstreetmap.josm.plugins.streetside.actions.StreetsideExportAction;
    1413import org.openstreetmap.josm.plugins.streetside.actions.StreetsideWalkAction;
    1514import org.openstreetmap.josm.plugins.streetside.actions.StreetsideZoomAction;
     
    2019import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.ImageInfoHelpPopup;
    2120import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.ImageInfoPanel;
    22 import org.openstreetmap.josm.plugins.streetside.oauth.StreetsideUser;
    23 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
    2421import org.openstreetmap.josm.tools.ImageProvider;
    2522
     
    2926public class StreetsidePlugin extends Plugin {
    3027
    31   public static final ImageProvider LOGO = new ImageProvider("streetside-logo");
     28    public static final ImageProvider LOGO = new ImageProvider("streetside-logo");
    3229
    33   /**
    34    * Zoom action
    35    */
    36   private static final StreetsideZoomAction ZOOM_ACTION = new StreetsideZoomAction();
    37   /**
    38    * Walk action
    39    */
    40   private static final StreetsideWalkAction WALK_ACTION = new StreetsideWalkAction();
     30    /**
     31     * Zoom action
     32     */
     33    private static final StreetsideZoomAction ZOOM_ACTION = new StreetsideZoomAction();
     34    /**
     35     * Walk action
     36     */
     37    private static final StreetsideWalkAction WALK_ACTION = new StreetsideWalkAction();
    4138
    42   /**
    43    * Main constructor.
    44    *
    45    * @param info Required information of the plugin. Obtained from the jar file.
    46    */
    47   public StreetsidePlugin(PluginInformation info) {
    48     super(info);
     39    /**
     40     * Main constructor.
     41     *
     42     * @param info Required information of the plugin. Obtained from the jar file.
     43     */
     44    public StreetsidePlugin(PluginInformation info) {
     45        super(info);
    4946
    50     if (StreetsideProperties.ACCESS_TOKEN.get() == null) {
    51       StreetsideUser.setTokenValid(false);
     47        MainMenu.add(MainApplication.getMenu().imagerySubMenu, new StreetsideDownloadAction(), false);
     48        MainMenu.add(MainApplication.getMenu().viewMenu, ZOOM_ACTION, false, 15);
     49        MainMenu.add(MainApplication.getMenu().fileMenu, new StreetsideDownloadViewAction(), false, 14);
     50        MainMenu.add(MainApplication.getMenu().moreToolsMenu, WALK_ACTION, false);
    5251    }
    53     MainMenu.add(MainApplication.getMenu().fileMenu, new StreetsideExportAction(), false, 14);
    54     MainMenu.add(MainApplication.getMenu().imagerySubMenu, new StreetsideDownloadAction(), false);
    55     MainMenu.add(MainApplication.getMenu().viewMenu, ZOOM_ACTION, false, 15);
    56     MainMenu.add(MainApplication.getMenu().fileMenu, new StreetsideDownloadViewAction(), false, 14);
    57     MainMenu.add(MainApplication.getMenu().moreToolsMenu, WALK_ACTION, false);
    58   }
    5952
    60   static StreetsideDataListener[] getStreetsideDataListeners() {
    61     return new StreetsideDataListener[] { WALK_ACTION, ZOOM_ACTION, CubemapBuilder.getInstance() };
    62   }
     53    static StreetsideDataListener[] getStreetsideDataListeners() {
     54        return new StreetsideDataListener[] { WALK_ACTION, ZOOM_ACTION, CubemapBuilder.getInstance() };
     55    }
    6356
    64   /**
    65    * @return the {@link StreetsideWalkAction} for the plugin
    66    */
    67   public static StreetsideWalkAction getStreetsideWalkAction() {
    68     return WALK_ACTION;
    69   }
     57    /**
     58     * Get the walk action for the plugin
     59     * @return the {@link StreetsideWalkAction} for the plugin
     60     */
     61    public static StreetsideWalkAction getStreetsideWalkAction() {
     62        return WALK_ACTION;
     63    }
    7064
    71   /**
    72    * @return the current {@link MapView} without throwing a {@link NullPointerException}
    73    */
    74   public static MapView getMapView() {
    75     final MapFrame mf = MainApplication.getMap();
    76     if (mf != null) {
    77       return mf.mapView;
     65    /**
     66     * Get the current mapview
     67     * @return the current {@link MapView} without throwing a {@link NullPointerException}
     68     */
     69    public static MapView getMapView() {
     70        final MapFrame mf = MainApplication.getMap();
     71        if (mf != null) {
     72            return mf.mapView;
     73        }
     74        return null;
    7875    }
    79     return null;
    80   }
    8176
    82   /**
    83    * Called when the JOSM map frame is created or destroyed.
    84    */
    85   @Override
    86   public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
    87     if (oldFrame == null && newFrame != null) { // map frame added
    88       MainApplication.getMap().addToggleDialog(StreetsideMainDialog.getInstance(), false);
    89       StreetsideMainDialog.getInstance().setImageInfoHelp(new ImageInfoHelpPopup(
    90           MainApplication.getMap().addToggleDialog(ImageInfoPanel.getInstance(), false)));
    91       MainApplication.getMap().addToggleDialog(StreetsideViewerDialog.getInstance(), false);
     77    /**
     78     * Called when the JOSM map frame is created or destroyed.
     79     */
     80    @Override
     81    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
     82        if (oldFrame == null && newFrame != null) { // map frame added
     83            MainApplication.getMap().addToggleDialog(StreetsideMainDialog.getInstance(), false);
     84            StreetsideMainDialog.getInstance().setImageInfoHelp(new ImageInfoHelpPopup(
     85                    MainApplication.getMap().addToggleDialog(ImageInfoPanel.getInstance(), false)));
     86            MainApplication.getMap().addToggleDialog(StreetsideViewerDialog.getInstance(), false);
     87        }
     88        if (oldFrame != null && newFrame == null) { // map frame destroyed
     89            StreetsideMainDialog.destroyInstance();
     90            ImageInfoPanel.destroyInstance();
     91            CubemapBuilder.destroyInstance();
     92            StreetsideViewerDialog.destroyInstance();
     93        }
    9294    }
    93     if (oldFrame != null && newFrame == null) { // map frame destroyed
    94       StreetsideMainDialog.destroyInstance();
    95       ImageInfoPanel.destroyInstance();
    96       CubemapBuilder.destroyInstance();
    97       StreetsideViewerDialog.destroyInstance();
     95
     96    @Override
     97    public PreferenceSetting getPreferenceSetting() {
     98        return new StreetsidePreferenceSetting();
    9899    }
    99   }
    100 
    101   @Override
    102   public PreferenceSetting getPreferenceSetting() {
    103     return new StreetsidePreferenceSetting();
    104   }
    105100}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/ImageReloadAction.java

    r36194 r36228  
    33
    44import java.awt.event.ActionEvent;
     5import java.io.Serial;
    56
    67import javax.swing.AbstractAction;
     
    1314public class ImageReloadAction extends AbstractAction {
    1415
    15   private static final long serialVersionUID = 7987479726049238315L;
     16    @Serial
     17    private static final long serialVersionUID = 7987479726049238315L;
    1618
    17   public ImageReloadAction(final String name) {
    18     super(name, ImageProvider.get("reload", ImageSizes.SMALLICON));
    19   }
     19    public ImageReloadAction(final String name) {
     20        super(name, ImageProvider.get("reload", ImageSizes.SMALLICON));
     21    }
    2022
    21   @Override
    22   public void actionPerformed(ActionEvent arg0) {
    23     if (StreetsideMainDialog.getInstance().getImage() != null) {
    24       CubemapBuilder.getInstance().reload(CubemapBuilder.getInstance().getCubemap().getId());
     23    @Override
     24    public void actionPerformed(ActionEvent arg0) {
     25        if (StreetsideMainDialog.getInstance().getImage() != null) {
     26            CubemapBuilder.getInstance().reload(CubemapBuilder.getInstance().getCubemap());
     27        }
    2528    }
    26   }
    2729}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideDownloadAction.java

    r36194 r36228  
    66import java.awt.event.ActionEvent;
    77import java.awt.event.KeyEvent;
     8import java.io.Serial;
    89import java.util.logging.Level;
    910import java.util.logging.Logger;
     
    2728public class StreetsideDownloadAction extends JosmAction {
    2829
    29   public static final Shortcut SHORTCUT = Shortcut.registerShortcut("Streetside", "Open Streetside layer",
    30       KeyEvent.VK_COMMA, Shortcut.SHIFT);
    31   private static final long serialVersionUID = 4426446157849005029L;
    32   private static final Logger LOGGER = Logger.getLogger(StreetsideDownloadAction.class.getCanonicalName());
     30    public static final Shortcut SHORTCUT = Shortcut.registerShortcut("Streetside", "Open Streetside layer",
     31            KeyEvent.VK_COMMA, Shortcut.SHIFT);
     32    @Serial
     33    private static final long serialVersionUID = 4426446157849005029L;
     34    private static final Logger LOGGER = Logger.getLogger(StreetsideDownloadAction.class.getCanonicalName());
    3335
    34   /**
    35    * Main constructor.
    36    */
    37   public StreetsideDownloadAction() {
    38     super(tr("Streetside"), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT),
    39         tr("Open Streetside layer"), SHORTCUT, false, "streetsideDownload", false);
    40   }
    41 
    42   @Override
    43   public void actionPerformed(ActionEvent ae) {
    44     if (!StreetsideLayer.hasInstance()
    45         || !MainApplication.getLayerManager().containsLayer(StreetsideLayer.getInstance())) {
    46       MainApplication.getLayerManager().addLayer(StreetsideLayer.getInstance());
    47       return;
     36    /**
     37     * Main constructor.
     38     */
     39    public StreetsideDownloadAction() {
     40        super(tr("Streetside"), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT),
     41                tr("Open Streetside layer"), SHORTCUT, false, "streetsideDownload", false);
    4842    }
    4943
    50     try {
    51       // Successive calls to this action toggle the active layer between the OSM data layer and the streetside layer
    52       OsmDataLayer editLayer = MainApplication.getLayerManager().getEditLayer();
    53       if (MainApplication.getLayerManager().getActiveLayer() != StreetsideLayer.getInstance()) {
    54         MainApplication.getLayerManager().setActiveLayer(StreetsideLayer.getInstance());
    55       } else if (editLayer != null) {
    56         MainApplication.getLayerManager().setActiveLayer(editLayer);
    57       }
    58     } catch (IllegalArgumentException e) {
    59       // If the StreetsideLayer is not managed by LayerManager but you try to set it as active layer
    60       LOGGER.log(Level.WARNING, e.getMessage(), e);
     44    @Override
     45    public void actionPerformed(ActionEvent ae) {
     46        if (!StreetsideLayer.hasInstance()
     47                || !MainApplication.getLayerManager().containsLayer(StreetsideLayer.getInstance())) {
     48            MainApplication.getLayerManager().addLayer(StreetsideLayer.getInstance());
     49            return;
     50        }
     51
     52        try {
     53            // Successive calls to this action toggle the active layer between the OSM data layer and the streetside layer
     54            OsmDataLayer editLayer = MainApplication.getLayerManager().getEditLayer();
     55            if (MainApplication.getLayerManager().getActiveLayer() != StreetsideLayer.getInstance()) {
     56                MainApplication.getLayerManager().setActiveLayer(StreetsideLayer.getInstance());
     57            } else if (editLayer != null) {
     58                MainApplication.getLayerManager().setActiveLayer(editLayer);
     59            }
     60        } catch (IllegalArgumentException e) {
     61            // If the StreetsideLayer is not managed by LayerManager, but you try to set it as active layer
     62            LOGGER.log(Level.WARNING, e.getMessage(), e);
     63        }
    6164    }
    62   }
    6365}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideDownloadViewAction.java

    r36194 r36228  
    44import java.awt.event.ActionEvent;
    55import java.awt.event.KeyEvent;
     6import java.io.Serial;
    67
    78import org.openstreetmap.josm.actions.JosmAction;
     
    2526public class StreetsideDownloadViewAction extends JosmAction implements ValueChangeListener<String> {
    2627
    27   private static final long serialVersionUID = 6738276777802831669L;
     28    @Serial
     29    private static final long serialVersionUID = 6738276777802831669L;
    2830
    29   private static final String DESCRIPTION = I18n.marktr("Download Streetside images in current view");
     31    private static final String DESCRIPTION = I18n.marktr("Download Streetside images in current view");
    3032
    31   /**
    32    * Main constructor.
    33    */
    34   public StreetsideDownloadViewAction() {
    35     super(I18n.tr(DESCRIPTION), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT),
    36         I18n.tr(DESCRIPTION),
    37         Shortcut.registerShortcut("Streetside area", I18n.tr(DESCRIPTION), KeyEvent.VK_PERIOD, Shortcut.SHIFT),
    38         false, "streetsideArea", true);
    39     StreetsideProperties.DOWNLOAD_MODE.addListener(this);
    40     initEnabledState();
    41   }
     33    /**
     34     * Main constructor.
     35     */
     36    public StreetsideDownloadViewAction() {
     37        super(I18n.tr(DESCRIPTION), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT),
     38                I18n.tr(DESCRIPTION),
     39                Shortcut.registerShortcut("Streetside area", I18n.tr(DESCRIPTION), KeyEvent.VK_PERIOD, Shortcut.SHIFT),
     40                false, "streetsideArea", true);
     41        StreetsideProperties.DOWNLOAD_MODE.addListener(this);
     42        initEnabledState();
     43    }
    4244
    43   @Override
    44   public void actionPerformed(ActionEvent arg0) {
    45     StreetsideDownloader.downloadVisibleArea();
    46   }
     45    @Override
     46    public void actionPerformed(ActionEvent arg0) {
     47        StreetsideDownloader.downloadVisibleArea();
     48    }
    4749
    48   @Override
    49   protected boolean listenToSelectionChange() {
    50     return false;
    51   }
     50    @Override
     51    protected boolean listenToSelectionChange() {
     52        return false;
     53    }
    5254
    53   /**
    54    * Enabled when the Streetside layer is instantiated and download mode is either "osm area" or "manual".
    55    */
    56   @Override
    57   protected void updateEnabledState() {
    58     super.updateEnabledState();
    59     setEnabled(StreetsideLayer.hasInstance()
    60         && (StreetsideDownloader.getMode() == StreetsideDownloader.DOWNLOAD_MODE.OSM_AREA
    61             || StreetsideDownloader.getMode() == StreetsideDownloader.DOWNLOAD_MODE.MANUAL_ONLY));
    62   }
     55    /**
     56     * Enabled when the Streetside layer is instantiated and download mode is either "osm area" or "manual".
     57     */
     58    @Override
     59    protected void updateEnabledState() {
     60        super.updateEnabledState();
     61        setEnabled(StreetsideLayer.hasInstance()
     62                && (StreetsideDownloader.getMode() == StreetsideDownloader.DOWNLOAD_MODE.OSM_AREA
     63                        || StreetsideDownloader.getMode() == StreetsideDownloader.DOWNLOAD_MODE.MANUAL_ONLY));
     64    }
    6365
    64   @Override
    65   public void valueChanged(ValueChangeEvent<? extends String> e) {
    66     updateEnabledState();
    67   }
     66    @Override
     67    public void valueChanged(ValueChangeEvent<? extends String> e) {
     68        updateEnabledState();
     69    }
    6870}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideWalkAction.java

    r36194 r36228  
    66import java.awt.Dimension;
    77import java.awt.event.ActionEvent;
     8import java.io.Serial;
    89import java.util.ArrayList;
    910import java.util.List;
     
    1415import org.openstreetmap.josm.actions.JosmAction;
    1516import org.openstreetmap.josm.gui.MainApplication;
    16 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    1717import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener;
     18import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
    1819import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
    1920import org.openstreetmap.josm.plugins.streetside.StreetsidePlugin;
     
    3132public class StreetsideWalkAction extends JosmAction implements StreetsideDataListener {
    3233
    33   private static final long serialVersionUID = 3454223919402245818L;
    34   private final List<WalkListener> listeners = new ArrayList<>();
    35   private WalkThread thread;
     34    @Serial
     35    private static final long serialVersionUID = 3454223919402245818L;
     36    private final List<WalkListener> listeners = new ArrayList<>();
     37    private WalkThread thread;
    3638
    37   /**
    38    *
    39    */
    40   public StreetsideWalkAction() {
    41     super(tr("Walk mode"), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT), tr("Walk mode"),
    42         null, false, "streetsideWalk", true);
    43   }
     39    /**
     40     * Automatically go through images
     41     */
     42    public StreetsideWalkAction() {
     43        super(tr("Walk mode"), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT).setOptional(true), tr("Walk mode"),
     44                null, false, "streetsideWalk", true);
     45    }
    4446
    45   @Override
    46   public void actionPerformed(ActionEvent arg0) {
    47     StreetsideWalkDialog dialog = new StreetsideWalkDialog();
    48     JOptionPane pane = new JOptionPane(dialog, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
    49     JDialog dlg = pane.createDialog(MainApplication.getMainFrame(), tr("Walk mode"));
    50     dlg.setMinimumSize(new Dimension(400, 150));
    51     dlg.setVisible(true);
    52     if (pane.getValue() != null && (int) pane.getValue() == JOptionPane.OK_OPTION) {
    53       thread = new WalkThread((int) dialog.spin.getValue(), dialog.waitForPicture.isSelected(),
    54           dialog.followSelection.isSelected(), dialog.goForward.isSelected());
    55       fireWalkStarted();
    56       thread.start();
    57       StreetsideMainDialog.getInstance().setMode(StreetsideMainDialog.MODE.WALK);
     47    @Override
     48    public void actionPerformed(ActionEvent arg0) {
     49        StreetsideWalkDialog dialog = new StreetsideWalkDialog();
     50        JOptionPane pane = new JOptionPane(dialog, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
     51        JDialog dlg = pane.createDialog(MainApplication.getMainFrame(), tr("Walk mode"));
     52        dlg.setMinimumSize(new Dimension(400, 150));
     53        dlg.setVisible(true);
     54        if (pane.getValue() != null && (int) pane.getValue() == JOptionPane.OK_OPTION) {
     55            thread = new WalkThread((int) dialog.spin.getValue(), dialog.waitForPicture.isSelected(),
     56                    dialog.followSelection.isSelected(), dialog.goForward.isSelected());
     57            fireWalkStarted();
     58            thread.start();
     59            StreetsideMainDialog.getInstance().setMode(StreetsideMainDialog.MODE.WALK);
     60        }
    5861    }
    59   }
    6062
    61   @Override
    62   public void imagesAdded() {
    63     // Nothing
    64   }
     63    @Override
     64    public void imagesAdded() {
     65        // Nothing
     66    }
    6567
    66   /**
    67    * Adds a listener.
    68    *
    69    * @param lis The listener to be added.
    70    */
    71   public void addListener(WalkListener lis) {
    72     listeners.add(lis);
    73   }
     68    /**
     69     * Adds a listener.
     70     *
     71     * @param lis The listener to be added.
     72     */
     73    public void addListener(WalkListener lis) {
     74        listeners.add(lis);
     75    }
    7476
    75   /**
    76    * Removes a listener.
    77    *
    78    * @param lis
    79    *      The listener to be added.
    80    */
    81   public void removeListener(WalkListener lis) {
    82     listeners.remove(lis);
    83   }
     77    /**
     78     * Removes a listener.
     79     *
     80     * @param lis
     81     *      The listener to be added.
     82     */
     83    public void removeListener(WalkListener lis) {
     84        listeners.remove(lis);
     85    }
    8486
    85   private void fireWalkStarted() {
    86     if (listeners.isEmpty()) {
    87       return;
     87    private void fireWalkStarted() {
     88        if (listeners.isEmpty()) {
     89            return;
     90        }
     91        for (WalkListener lis : listeners) {
     92            lis.walkStarted(thread);
     93        }
    8894    }
    89     for (WalkListener lis : listeners) {
    90       lis.walkStarted(thread);
     95
     96    @Override
     97    protected boolean listenToSelectionChange() {
     98        return false;
    9199    }
    92   }
    93100
    94   @Override
    95   protected boolean listenToSelectionChange() {
    96     return false;
    97   }
     101    @Override
     102    public void selectedImageChanged(StreetsideImage oldImage, StreetsideImage newImage) {
     103        if (oldImage == null && newImage != null) {
     104            setEnabled(true);
     105        } else if (oldImage != null && newImage == null) {
     106            setEnabled(false);
     107        }
     108    }
    98109
    99   @Override
    100   public void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) {
    101     if (oldImage == null && newImage != null) {
    102       setEnabled(true);
    103     } else if (oldImage != null && newImage == null) {
    104       setEnabled(false);
     110    /**
     111     * Enabled when a mapillary image is selected.
     112     */
     113    @Override
     114    protected void updateEnabledState() {
     115        super.updateEnabledState();
     116        setEnabled(StreetsideLayer.hasInstance() && StreetsideLayer.getInstance().getData().getSelectedImage() != null);
    105117    }
    106   }
    107 
    108   /**
    109    * Enabled when a mapillary image is selected.
    110    */
    111   @Override
    112   protected void updateEnabledState() {
    113     super.updateEnabledState();
    114     setEnabled(StreetsideLayer.hasInstance() && StreetsideLayer.getInstance().getData().getSelectedImage() != null);
    115   }
    116118
    117119}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideZoomAction.java

    r36194 r36228  
    55
    66import java.awt.event.ActionEvent;
     7import java.io.Serial;
    78
    89import org.openstreetmap.josm.actions.JosmAction;
    910import org.openstreetmap.josm.gui.MainApplication;
    10 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    1111import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener;
     12import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
    1213import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
    1314import org.openstreetmap.josm.plugins.streetside.StreetsidePlugin;
     
    2324public class StreetsideZoomAction extends JosmAction implements StreetsideDataListener {
    2425
    25   private static final long serialVersionUID = -5885977359895624233L;
     26    @Serial
     27    private static final long serialVersionUID = -5885977359895624233L;
    2628
    27   /**
    28    * Main constructor.
    29    */
    30   public StreetsideZoomAction() {
    31     super(tr("Zoom to selected image"), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT),
    32         tr("Zoom to the currently selected Streetside image"), null, false, "mapillaryZoom", true);
    33   }
     29    /**
     30     * Main constructor.
     31     */
     32    public StreetsideZoomAction() {
     33        super(tr("Zoom to selected image"), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT).setOptional(true),
     34                tr("Zoom to the currently selected Streetside image"), null, false, "mapillaryZoom", true);
     35    }
    3436
    35   @Override
    36   public void actionPerformed(ActionEvent arg0) {
    37     if (StreetsideLayer.getInstance().getData().getSelectedImage() == null) {
    38       throw new IllegalStateException();
     37    @Override
     38    public void actionPerformed(ActionEvent arg0) {
     39        if (StreetsideLayer.getInstance().getData().getSelectedImage() == null) {
     40            throw new IllegalStateException();
     41        }
     42        MainApplication.getMap().mapView.zoomTo(StreetsideLayer.getInstance().getData().getSelectedImage());
    3943    }
    40     MainApplication.getMap().mapView
    41         .zoomTo(StreetsideLayer.getInstance().getData().getSelectedImage().getMovingLatLon());
    42   }
    4344
    44   @Override
    45   public void imagesAdded() {
    46     // Nothing
    47   }
     45    @Override
     46    public void imagesAdded() {
     47        // Nothing
     48    }
    4849
    49   @Override
    50   protected boolean listenToSelectionChange() {
    51     return false;
    52   }
     50    @Override
     51    protected boolean listenToSelectionChange() {
     52        return false;
     53    }
    5354
    54   @Override
    55   public void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) {
    56     if (oldImage == null && newImage != null) {
    57       setEnabled(true);
    58     } else if (oldImage != null && newImage == null) {
    59       setEnabled(false);
     55    @Override
     56    public void selectedImageChanged(StreetsideImage oldImage, StreetsideImage newImage) {
     57        if (oldImage == null && newImage != null) {
     58            setEnabled(true);
     59        } else if (oldImage != null && newImage == null) {
     60            setEnabled(false);
     61        }
    6062    }
    61   }
    6263
    63   @Override
    64   protected void updateEnabledState() {
    65     super.updateEnabledState();
    66     setEnabled(StreetsideLayer.hasInstance() && StreetsideLayer.getInstance().getData().getSelectedImage() != null);
    67   }
     64    @Override
     65    protected void updateEnabledState() {
     66        super.updateEnabledState();
     67        setEnabled(StreetsideLayer.hasInstance() && StreetsideLayer.getInstance().getData().getSelectedImage() != null);
     68    }
    6869}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/WalkListener.java

    r34317 r36228  
    1111public interface WalkListener {
    1212
    13   /**
    14    * Called when a new walk thread is started.
    15    *
    16    * @param thread The thread executing the walk.
    17    */
    18   void walkStarted(WalkThread thread);
     13    /**
     14     * Called when a new walk thread is started.
     15     *
     16     * @param thread The thread executing the walk.
     17     */
     18    void walkStarted(WalkThread thread);
    1919}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/WalkThread.java

    r36194 r36228  
    88import javax.swing.SwingUtilities;
    99
    10 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    1110import org.openstreetmap.josm.plugins.streetside.StreetsideData;
    1211import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener;
     
    2423 */
    2524public class WalkThread extends Thread implements StreetsideDataListener {
    26   private static final Logger LOGGER = Logger.getLogger(WalkThread.class.getCanonicalName());
    27   private final int interval;
    28   private final StreetsideData data;
    29   private final boolean waitForFullQuality;
    30   private final boolean followSelected;
    31   private final boolean goForward;
    32   private boolean end;
    33   private BufferedImage lastImage;
    34   private volatile boolean paused;
     25    private static final Logger LOGGER = Logger.getLogger(WalkThread.class.getCanonicalName());
     26    private final int interval;
     27    private final StreetsideData data;
     28    private final boolean waitForFullQuality;
     29    private final boolean followSelected;
     30    private final boolean goForward;
     31    private boolean end;
     32    private BufferedImage lastImage;
     33    private volatile boolean paused;
    3534
    36   /**
    37    * Main constructor.
    38    *
    39    * @param interval     How often the images switch.
    40    * @param waitForPicture If it must wait for the full resolution picture or just the
    41    *             thumbnail.
    42    * @param followSelected Zoom to each image that is selected.
    43    * @param goForward    true to go forward; false to go backwards.
    44    */
    45   public WalkThread(int interval, boolean waitForPicture, boolean followSelected, boolean goForward) {
    46     this.interval = interval;
    47     waitForFullQuality = waitForPicture;
    48     this.followSelected = followSelected;
    49     this.goForward = goForward;
    50     data = StreetsideLayer.getInstance().getData();
    51     data.addListener(this);
    52   }
     35    /**
     36     * Main constructor.
     37     *
     38     * @param interval     How often the images switch.
     39     * @param waitForPicture If it must wait for the full resolution picture or just the
     40     *             thumbnail.
     41     * @param followSelected Zoom to each image that is selected.
     42     * @param goForward    true to go forward; false to go backwards.
     43     */
     44    public WalkThread(int interval, boolean waitForPicture, boolean followSelected, boolean goForward) {
     45        this.interval = interval;
     46        waitForFullQuality = waitForPicture;
     47        this.followSelected = followSelected;
     48        this.goForward = goForward;
     49        data = StreetsideLayer.getInstance().getData();
     50        data.addListener(this);
     51    }
    5352
    54   /**
    55    * Downloads n images into the cache beginning from the supplied start-image (including the start-image itself).
    56    *
    57    * @param startImage the image to start with (this and the next n-1 images in the same sequence are downloaded)
    58    * @param n      the number of images to download
    59    * @param type     the quality of the image (full or thumbnail)
    60    */
    61   private static void preDownloadImages(StreetsideImage startImage, int n, CacheUtils.PICTURE type) {
    62     if (n >= 1 && startImage != null) {
    63       CacheUtils.downloadPicture(startImage, type);
    64       if (startImage.next() instanceof StreetsideImage && n >= 2) {
    65         preDownloadImages((StreetsideImage) startImage.next(), n - 1, type);
    66       }
     53    /**
     54     * Downloads n images into the cache beginning from the supplied start-image (including the start-image itself).
     55     *
     56     * @param startImage the image to start with (this and the next n-1 images in the same sequence are downloaded)
     57     * @param n      the number of images to download
     58     * @param type     the quality of the image (full or thumbnail)
     59     */
     60    private void preDownloadImages(StreetsideImage startImage, int n, CacheUtils.PICTURE type) {
     61        if (n >= 1 && startImage != null) {
     62            CacheUtils.downloadPicture(startImage, type);
     63            if (this.data.next(startImage) != null && n >= 2) {
     64                preDownloadImages(this.data.next(startImage), n - 1, type);
     65            }
     66        }
    6767    }
    68   }
    6968
    70   @Override
    71   public void run() {
    72     try {
    73       while (!end && data.getSelectedImage().next() != null) {
    74         StreetsideAbstractImage image = data.getSelectedImage();
    75         if (image != null && image.next() instanceof StreetsideImage) {
    76           // Predownload next 10 thumbnails.
    77           preDownloadImages((StreetsideImage) image.next(), 10, CacheUtils.PICTURE.THUMBNAIL);
    78           if (Boolean.TRUE.equals(StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get())) {
    79             preDownloadCubemaps((StreetsideImage) image.next(), 10);
    80           }
    81           if (waitForFullQuality) {
    82             // Start downloading 3 next full images.
    83             StreetsideAbstractImage currentImage = image.next();
    84             preDownloadImages((StreetsideImage) currentImage, 3, CacheUtils.PICTURE.FULL_IMAGE);
    85           }
     69    @Override
     70    public void run() {
     71        try {
     72            while (!end && this.data.next(this.data.getSelectedImage()) != null) {
     73                StreetsideImage image = data.getSelectedImage();
     74                if (image != null && this.data.next(image) != null) {
     75                    // Predownload next 10 thumbnails.
     76                    preDownloadImages(this.data.next(image), 10, CacheUtils.PICTURE.THUMBNAIL);
     77                    if (Boolean.TRUE.equals(StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get())) {
     78                        preDownloadCubemaps(this.data.next(image), 10);
     79                    }
     80                    if (waitForFullQuality) {
     81                        // Start downloading 3 next full images.
     82                        StreetsideImage currentImage = this.data.next(image);
     83                        preDownloadImages(currentImage, 3, CacheUtils.PICTURE.FULL_IMAGE);
     84                    }
     85                }
     86                try {
     87                    // Waits for full quality picture.
     88                    final BufferedImage displayImage = StreetsideMainDialog.getInstance().getStreetsideImageDisplay()
     89                            .getImage();
     90                    if (waitForFullQuality && image != null) {
     91                        while (displayImage == lastImage || displayImage == null || displayImage.getWidth() < 2048) {
     92                            Thread.sleep(100);
     93                        }
     94                    } else { // Waits for thumbnail.
     95                        while (displayImage == lastImage || displayImage == null || displayImage.getWidth() < 320) {
     96                            Thread.sleep(100);
     97                        }
     98                    }
     99                    while (paused) {
     100                        Thread.sleep(100);
     101                    }
     102                    wait(interval);
     103                    while (paused) {
     104                        Thread.sleep(100);
     105                    }
     106                    lastImage = StreetsideMainDialog.getInstance().getStreetsideImageDisplay().getImage();
     107                    if (goForward) {
     108                        data.selectNext(followSelected);
     109                    } else {
     110                        data.selectPrevious(followSelected);
     111                    }
     112                } catch (InterruptedException e) {
     113                    return;
     114                }
     115            }
     116        } catch (NullPointerException e) {
     117            if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     118                LOGGER.log(Logging.LEVEL_DEBUG,
     119                        MessageFormat.format("Exception thrown in WalkThread: {0}", e.getMessage()), e);
     120            }
     121            return;
    86122        }
    87         try {
    88           // Waits for full quality picture.
    89           final BufferedImage displayImage = StreetsideMainDialog.getInstance().getStreetsideImageDisplay()
    90               .getImage();
    91           if (waitForFullQuality && image instanceof StreetsideImage) {
    92             while (displayImage == lastImage || displayImage == null || displayImage.getWidth() < 2048) {
    93               Thread.sleep(100);
     123        end();
     124    }
     125
     126    private void preDownloadCubemaps(StreetsideImage startImage, int n) {
     127        if (n >= 1 && startImage != null) {
     128
     129            for (int i = 0; i < 6; i++) {
     130                for (int j = 0; j < 4; j++) {
     131                    for (int k = 0; k < 4; k++) {
     132
     133                        CacheUtils.downloadPicture(startImage, CacheUtils.PICTURE.CUBEMAP);
     134                        if (this.data.next(startImage) != null && n >= 2) {
     135                            preDownloadCubemaps(this.data.next(startImage), n - 1);
     136                        }
     137                    }
     138                }
    94139            }
    95           } else { // Waits for thumbnail.
    96             while (displayImage == lastImage || displayImage == null || displayImage.getWidth() < 320) {
    97               Thread.sleep(100);
    98             }
    99           }
    100           while (paused) {
    101             Thread.sleep(100);
    102           }
    103           wait(interval);
    104           while (paused) {
    105             Thread.sleep(100);
    106           }
    107           lastImage = StreetsideMainDialog.getInstance().getStreetsideImageDisplay().getImage();
    108           if (goForward) {
    109             data.selectNext(followSelected);
    110           } else {
    111             data.selectPrevious(followSelected);
    112           }
    113         } catch (InterruptedException e) {
    114           return;
    115140        }
    116       }
    117     } catch (NullPointerException e) {
    118       if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    119         LOGGER.log(Logging.LEVEL_DEBUG,
    120             MessageFormat.format("Exception thrown in WalkThread: {0}", e.getMessage()), e);
    121       }
    122       return;
    123141    }
    124     end();
    125   }
    126142
    127   private void preDownloadCubemaps(StreetsideImage startImage, int n) {
    128     if (n >= 1 && startImage != null) {
     143    @Override
     144    public void imagesAdded() {
     145        // Nothing
     146    }
    129147
    130       for (int i = 0; i < 6; i++) {
    131         for (int j = 0; j < 4; j++) {
    132           for (int k = 0; k < 4; k++) {
     148    @Override
     149    public void selectedImageChanged(StreetsideImage oldImage, StreetsideImage newImage) {
     150        if (newImage != this.data.next(oldImage)) {
     151            end();
     152            interrupt();
     153        }
     154    }
    133155
    134             CacheUtils.downloadPicture(startImage, CacheUtils.PICTURE.CUBEMAP);
    135             if (startImage.next() instanceof StreetsideImage && n >= 2) {
    136               preDownloadCubemaps((StreetsideImage) startImage.next(), n - 1);
    137             }
    138           }
     156    /**
     157     * Continues with the execution if paused.
     158     */
     159    public void play() {
     160        paused = false;
     161    }
     162
     163    /**
     164     * Pauses the execution.
     165     */
     166    public void pause() {
     167        paused = true;
     168    }
     169
     170    /**
     171     * Stops the execution.
     172     */
     173    public void stopWalk() {
     174        if (SwingUtilities.isEventDispatchThread()) {
     175            end();
     176            interrupt();
     177        } else {
     178            SwingUtilities.invokeLater(this::stopWalk);
    139179        }
    140       }
    141180    }
    142   }
    143181
    144   @Override
    145   public void imagesAdded() {
    146     // Nothing
    147   }
    148 
    149   @Override
    150   public void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) {
    151     if (newImage != oldImage.next()) {
    152       end();
    153       interrupt();
     182    /**
     183     * Called when the walk stops by itself of forcefully.
     184     */
     185    public void end() {
     186        if (SwingUtilities.isEventDispatchThread()) {
     187            end = true;
     188            data.removeListener(this);
     189            StreetsideMainDialog.getInstance().setMode(StreetsideMainDialog.MODE.NORMAL);
     190        } else {
     191            SwingUtilities.invokeLater(this::end);
     192        }
    154193    }
    155   }
    156 
    157   /**
    158    * Continues with the execution if paused.
    159    */
    160   public void play() {
    161     paused = false;
    162   }
    163 
    164   /**
    165    * Pauses the execution.
    166    */
    167   public void pause() {
    168     paused = true;
    169   }
    170 
    171   /**
    172    * Stops the execution.
    173    */
    174   public void stopWalk() {
    175     if (SwingUtilities.isEventDispatchThread()) {
    176       end();
    177       interrupt();
    178     } else {
    179       SwingUtilities.invokeLater(this::stopWalk);
    180     }
    181   }
    182 
    183   /**
    184    * Called when the walk stops by itself of forcefully.
    185    */
    186   public void end() {
    187     if (SwingUtilities.isEventDispatchThread()) {
    188       end = true;
    189       data.removeListener(this);
    190       StreetsideMainDialog.getInstance().setMode(StreetsideMainDialog.MODE.NORMAL);
    191     } else {
    192       SwingUtilities.invokeLater(this::end);
    193     }
    194   }
    195194}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cache/CacheUtils.java

    r36194 r36228  
    33
    44import java.io.IOException;
    5 import java.util.logging.Logger;
     5import java.io.UncheckedIOException;
     6import java.util.Collections;
     7import java.util.HashMap;
     8import java.util.Map;
     9import java.util.stream.Collectors;
    610
    711import org.openstreetmap.josm.data.cache.CacheEntry;
    812import org.openstreetmap.josm.data.cache.CacheEntryAttributes;
    913import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
     14import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY;
    1015import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
    1116import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapBuilder;
    12 import org.openstreetmap.josm.tools.Logging;
     17import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils;
    1318
    1419/**
     
    2025public final class CacheUtils {
    2126
    22   private static final Logger LOGGER = Logger.getLogger(CacheUtils.class.getCanonicalName());
     27    private static final IgnoreDownload ignoreDownload = new IgnoreDownload();
    2328
    24   private static final IgnoreDownload ignoreDownload = new IgnoreDownload();
     29    private CacheUtils() {
     30        // Private constructor to avoid instantiation
     31    }
    2532
    26   private CacheUtils() {
    27     // Private constructor to avoid instantiation
    28   }
     33    /**
     34     * Downloads the thumbnail and the full resolution picture of the given
     35     * image. Does nothing if it is already in cache.
     36     *
     37     * @param img The image whose picture is going to be downloaded.
     38     * @return A map of tiles to cached images (The {@code null} key is the thumbnail, if one was requested)
     39     */
     40    public static Map<CubeMapTileXY, StreetsideCache> downloadPicture(StreetsideImage img) {
     41        return downloadPicture(img, PICTURE.BOTH);
     42    }
    2943
    30   /**
    31    * Downloads the the thumbnail and the full resolution picture of the given
    32    * image. Does nothing if it is already in cache.
    33    *
    34    * @param img The image whose picture is going to be downloaded.
    35    */
    36   public static void downloadPicture(StreetsideImage img) {
    37     downloadPicture(img, PICTURE.BOTH);
    38   }
     44    /**
     45     * Downloads the thumbnail and the full resolution picture of the given
     46     * image. Does nothing if it is already in cache.
     47     *
     48     * @param cm The image whose picture is going to be downloaded.
     49     * @return A map of tiles to cached images (The {@code null} key is the thumbnail, if one was requested)
     50     */
     51    public static Map<CubeMapTileXY, StreetsideCache> downloadCubemap(StreetsideImage cm) {
     52        return downloadPicture(cm, PICTURE.CUBEMAP);
     53    }
    3954
    40   /**
    41    * Downloads the the thumbnail and the full resolution picture of the given
    42    * image. Does nothing if it is already in cache.
    43    *
    44    * @param cm The image whose picture is going to be downloaded.
    45    */
    46   public static void downloadCubemap(StreetsideImage cm) {
    47     downloadPicture(cm, PICTURE.CUBEMAP);
    48   }
     55    /**
     56     * Downloads the picture of the given image. Does nothing when it is already
     57     * in cache.
     58     *
     59     * @param img The image to be downloaded.
     60     * @param pic The picture type to be downloaded (full quality, thumbnail or
     61     *      both.)
     62     * @return A map of tiles to cached images (The {@code null} key is the thumbnail, if one was requested)
     63     */
     64    public static Map<CubeMapTileXY, StreetsideCache> downloadPicture(StreetsideImage img, PICTURE pic) {
     65        return downloadPicture(img, pic, ignoreDownload);
     66    }
    4967
    50   /**
    51    * Downloads the picture of the given image. Does nothing when it is already
    52    * in cache.
    53    *
    54    * @param img The image to be downloaded.
    55    * @param pic The picture type to be downloaded (full quality, thumbnail or
    56    *      both.)
    57    */
    58   public static void downloadPicture(StreetsideImage img, PICTURE pic) {
    59     switch (pic) {
    60     case BOTH:
    61       if (new StreetsideCache(img.getId(), StreetsideCache.Type.THUMBNAIL).get() == null)
    62         submit(img.getId(), StreetsideCache.Type.THUMBNAIL, ignoreDownload);
    63       if (new StreetsideCache(img.getId(), StreetsideCache.Type.FULL_IMAGE).get() == null)
    64         submit(img.getId(), StreetsideCache.Type.FULL_IMAGE, ignoreDownload);
    65       break;
    66     case THUMBNAIL:
    67       submit(img.getId(), StreetsideCache.Type.THUMBNAIL, ignoreDownload);
    68       break;
    69     case FULL_IMAGE:
    70       // not used (relic from Mapillary)
    71       break;
    72     case CUBEMAP:
    73       if (img.getId() == null) {
    74         LOGGER.log(Logging.LEVEL_ERROR, "Download cancelled. Image id is null.");
    75       } else {
    76         CubemapBuilder.getInstance().downloadCubemapImages(img.getId());
    77       }
    78       break;
    79     default:
    80       submit(img.getId(), StreetsideCache.Type.FULL_IMAGE, ignoreDownload);
    81       break;
     68    /**
     69     * Downloads the picture of the given image. Does nothing when it is already
     70     * in cache.
     71     *
     72     * @param img The image to be downloaded.
     73     * @param pic The picture type to be downloaded (full quality, thumbnail or
     74     *      both.)
     75     * @param lis  The listener that is going to receive the picture.
     76     * @return A map of tiles to cached images (The {@code null} key is the thumbnail, if one was requested)
     77     */
     78    public static Map<CubeMapTileXY, StreetsideCache> downloadPicture(StreetsideImage img, PICTURE pic,
     79            ICachedLoaderListener lis) {
     80        if (img.id() == null) {
     81            return Collections.emptyMap();
     82        }
     83        return switch (pic) {
     84        case BOTH -> {
     85            final Map<CubeMapTileXY, StreetsideCache> jobs = new HashMap<>(
     86                    1 + (int) Math.round(Math.pow(4, img.zoomMax())));
     87            jobs.putAll(downloadPicture(img, PICTURE.THUMBNAIL, lis));
     88            jobs.putAll(downloadPicture(img, PICTURE.FULL_IMAGE, lis));
     89            yield Collections.unmodifiableMap(jobs);
     90        }
     91        case FULL_IMAGE -> img.getFaceTiles(CubemapUtils.CubemapFaces.FRONT, img.zoomMax())
     92                .collect(Collectors.toMap(p -> p.a, p -> submit(p.b, lis)));
     93        case CUBEMAP -> CubemapBuilder.getInstance().downloadCubemapImages(img);
     94        case THUMBNAIL -> Collections.singletonMap(null, submit(img.getThumbnail(), lis));
     95        };
    8296    }
    83   }
    8497
    85   /**
    86    * Requests the picture with the given key and quality and uses the given
    87    * listener.
    88    *
    89    * @param key  The key of the picture to be requested.
    90    * @param type The quality of the picture to be requested.
    91    * @param lis  The listener that is going to receive the picture.
    92    */
    93   public static void submit(String key, StreetsideCache.Type type, ICachedLoaderListener lis) {
    94     try {
    95       new StreetsideCache(key, type).submit(lis, false);
    96     } catch (IOException e) {
    97       LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     98    /**
     99     * Requests the picture with the given key and quality and uses the given
     100     * listener.
     101     *
     102     * @param key  The key of the picture to be requested.
     103     * @param lis  The listener that is going to receive the picture.
     104     * @return The cache job
     105     */
     106    public static StreetsideCache submit(String key, ICachedLoaderListener lis) {
     107        try {
     108            final var cache = new StreetsideCache(key);
     109            cache.submit(lis, false);
     110            return cache;
     111        } catch (IOException e) {
     112            throw new UncheckedIOException(e);
     113        }
    98114    }
    99   }
    100115
    101   /**
    102    * Picture quality
    103    */
    104   public enum PICTURE {
    105116    /**
    106      * Thumbnail quality picture (320 p)
     117     * Picture quality
    107118     */
    108     THUMBNAIL,
    109     /**
    110      * Full quality picture (2048 p)
    111      */
    112     FULL_IMAGE,
    113     /**
    114      * Both of them
    115      */
    116     BOTH,
    117     /**
    118      * Streetside cubemap
    119      */
    120     CUBEMAP
    121   }
     119    public enum PICTURE {
     120        /**
     121         * Thumbnail quality picture (320 p)
     122         */
     123        THUMBNAIL,
     124        /**
     125         * Full quality picture (2048 p)
     126         */
     127        FULL_IMAGE,
     128        /**
     129         * Both of them
     130         */
     131        BOTH,
     132        /**
     133         * Streetside cubemap
     134         */
     135        CUBEMAP
     136    }
    122137
    123   private static class IgnoreDownload implements ICachedLoaderListener {
     138    private static class IgnoreDownload implements ICachedLoaderListener {
    124139
    125     @Override
    126     public void loadingFinished(CacheEntry arg0, CacheEntryAttributes arg1, LoadResult arg2) {
    127       // Ignore download
     140        @Override
     141        public void loadingFinished(CacheEntry arg0, CacheEntryAttributes arg1, LoadResult arg2) {
     142            // Ignore download
     143        }
    128144    }
    129   }
    130145}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cache/Caches.java

    r36194 r36228  
    1010
    1111import org.apache.commons.jcs3.access.CacheAccess;
    12 import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
    1312import org.openstreetmap.josm.data.Preferences;
    1413import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
    1514import org.openstreetmap.josm.data.cache.JCSCacheManager;
    16 import org.openstreetmap.josm.plugins.streetside.model.UserProfile;
    1715import org.openstreetmap.josm.tools.Logging;
    1816
    1917public final class Caches {
    2018
    21   private static final Logger LOGGER = Logger.getLogger(Caches.class.getCanonicalName());
     19    private static final Logger LOGGER = Logger.getLogger(Caches.class.getCanonicalName());
    2220
    23   private Caches() {
    24     // Private constructor to avoid instantiation
    25   }
    26 
    27   public static File getCacheDirectory() {
    28     final File f = new File(Preferences.main().getPluginsDirectory().getPath() + "/MicrosoftStreetside/cache");
    29     if (!f.exists()) {
    30       f.mkdirs();
    31     }
    32     return f;
    33   }
    34 
    35   public abstract static class CacheProxy<K, V extends Serializable> {
    36     private final CacheAccess<K, V> cache;
    37 
    38     protected CacheProxy() {
    39       CacheAccess<K, V> c;
    40       try {
    41         c = createNewCache();
    42       } catch (IOException e) {
    43         LOGGER.log(Logging.LEVEL_WARN, e, () -> "Could not initialize cache for " + getClass().getName());
    44         c = null;
    45       }
    46       cache = c;
     21    private Caches() {
     22        // Private constructor to avoid instantiation
    4723    }
    4824
    49     protected abstract CacheAccess<K, V> createNewCache() throws IOException;
    50 
    51     public V get(final K key) {
    52       return cache == null ? null : cache.get(key);
     25    public static File getCacheDirectory() {
     26        final var file = new File(Preferences.main().getPluginsDirectory().getPath() + "/MicrosoftStreetside/cache");
     27        if (!file.exists()) {
     28            if (!file.mkdirs()) {
     29                Logging.error("Failed to make directory: {0}", file);
     30            }
     31        }
     32        return file;
    5333    }
    5434
    55     public void put(final K key, final V value) {
    56       if (cache != null) {
    57         cache.put(key, value);
    58       }
    59     }
    60   }
     35    public abstract static class CacheProxy<K, V extends Serializable> {
     36        private final CacheAccess<K, V> cache;
    6137
    62   public static class ImageCache {
    63     private static ImageCache instance;
    64     private final CacheAccess<String, BufferedImageCacheEntry> cache;
     38        protected CacheProxy() {
     39            CacheAccess<K, V> c;
     40            try {
     41                c = createNewCache();
     42            } catch (IOException e) {
     43                LOGGER.log(Logging.LEVEL_WARN, e, () -> "Could not initialize cache for " + getClass().getName());
     44                c = null;
     45            }
     46            cache = c;
     47        }
    6548
    66     public ImageCache() {
    67       CacheAccess<String, BufferedImageCacheEntry> c;
    68       try {
    69         c = JCSCacheManager.getCache("streetside", 10, 10000, Caches.getCacheDirectory().getPath());
    70       } catch (Exception e) {
    71         Logging.log(Logging.LEVEL_WARN, "Could not initialize the Streetside image cache.", e);
    72         c = null;
    73       }
    74       cache = c;
     49        protected abstract CacheAccess<K, V> createNewCache() throws IOException;
     50
     51        public V get(final K key) {
     52            return cache == null ? null : cache.get(key);
     53        }
     54
     55        public void put(final K key, final V value) {
     56            if (cache != null) {
     57                cache.put(key, value);
     58            }
     59        }
    7560    }
    7661
    77     public static ImageCache getInstance() {
    78       synchronized (ImageCache.class) {
    79         if (ImageCache.instance == null) {
    80           ImageCache.instance = new ImageCache();
     62    public static class ImageCache {
     63        private static ImageCache instance;
     64        private final CacheAccess<String, BufferedImageCacheEntry> cache;
     65
     66        public ImageCache() {
     67            CacheAccess<String, BufferedImageCacheEntry> c;
     68            try {
     69                c = JCSCacheManager.getCache("streetside", 10, 10000, Caches.getCacheDirectory().getPath());
     70            } catch (Exception e) {
     71                Logging.log(Logging.LEVEL_WARN, "Could not initialize the Streetside image cache.", e);
     72                c = null;
     73            }
     74            cache = c;
    8175        }
    82         return ImageCache.instance;
    83       }
     76
     77        public static ImageCache getInstance() {
     78            synchronized (ImageCache.class) {
     79                if (ImageCache.instance == null) {
     80                    ImageCache.instance = new ImageCache();
     81                }
     82                return ImageCache.instance;
     83            }
     84        }
     85
     86        public CacheAccess<String, BufferedImageCacheEntry> getCache() {
     87            return cache;
     88        }
    8489    }
    8590
    86     public CacheAccess<String, BufferedImageCacheEntry> getCache() {
    87       return cache;
    88     }
    89   }
     91    public static class CubemapCache {
     92        private static CubemapCache instance;
     93        private final CacheAccess<String, BufferedImageCacheEntry> cache;
    9094
    91   public static class CubemapCache {
    92     private static CubemapCache instance;
    93     private final CacheAccess<String, BufferedImageCacheEntry> cache;
     95        public CubemapCache() {
     96            CacheAccess<String, BufferedImageCacheEntry> c;
     97            try {
     98                c = JCSCacheManager.getCache("streetside", 10, 10000, Caches.getCacheDirectory().getPath());
     99            } catch (Exception e) {
     100                LOGGER.log(Logging.LEVEL_WARN, "Could not initialize the Streetside cubemap cache.", e);
     101                c = null;
     102            }
     103            cache = c;
     104        }
    94105
    95     public CubemapCache() {
    96       CacheAccess<String, BufferedImageCacheEntry> c;
    97       try {
    98         c = JCSCacheManager.getCache("streetside", 10, 10000, Caches.getCacheDirectory().getPath());
    99       } catch (Exception e) {
    100         LOGGER.log(Logging.LEVEL_WARN, "Could not initialize the Streetside cubemap cache.", e);
    101         c = null;
    102       }
    103       cache = c;
     106        public static CubemapCache getInstance() {
     107            synchronized (CubemapCache.class) {
     108                if (CubemapCache.instance == null) {
     109                    CubemapCache.instance = new CubemapCache();
     110                }
     111                return CubemapCache.instance;
     112            }
     113        }
     114
     115        public CacheAccess<String, BufferedImageCacheEntry> getCache() {
     116            return cache;
     117        }
    104118    }
    105119
    106     public static CubemapCache getInstance() {
    107       synchronized (CubemapCache.class) {
    108         if (CubemapCache.instance == null) {
    109           CubemapCache.instance = new CubemapCache();
     120    public static class MapObjectIconCache extends CacheProxy<String, ImageIcon> {
     121        private static CacheProxy<String, ImageIcon> instance;
     122
     123        public static CacheProxy<String, ImageIcon> getInstance() {
     124            synchronized (MapObjectIconCache.class) {
     125                if (MapObjectIconCache.instance == null) {
     126                    MapObjectIconCache.instance = new MapObjectIconCache();
     127                }
     128                return MapObjectIconCache.instance;
     129            }
    110130        }
    111         return CubemapCache.instance;
    112       }
     131
     132        @Override
     133        protected CacheAccess<String, ImageIcon> createNewCache() throws IOException {
     134            return JCSCacheManager.getCache("streetsideObjectIcons", 100, 1000, Caches.getCacheDirectory().getPath());
     135        }
    113136    }
    114 
    115     public CacheAccess<String, BufferedImageCacheEntry> getCache() {
    116       return cache;
    117     }
    118   }
    119 
    120   public static class MapObjectIconCache extends CacheProxy<String, ImageIcon> {
    121     private static CacheProxy<String, ImageIcon> instance;
    122 
    123     public static CacheProxy<String, ImageIcon> getInstance() {
    124       synchronized (MapObjectIconCache.class) {
    125         if (MapObjectIconCache.instance == null) {
    126           MapObjectIconCache.instance = new MapObjectIconCache();
    127         }
    128         return MapObjectIconCache.instance;
    129       }
    130     }
    131 
    132     @Override
    133     protected CacheAccess<String, ImageIcon> createNewCache() throws IOException {
    134       return JCSCacheManager.getCache("streetsideObjectIcons", 100, 1000, Caches.getCacheDirectory().getPath());
    135     }
    136   }
    137 
    138   public static class UserProfileCache extends CacheProxy<String, UserProfile> {
    139     private static CacheProxy<String, UserProfile> instance;
    140 
    141     public static CacheProxy<String, UserProfile> getInstance() {
    142       synchronized (UserProfileCache.class) {
    143         if (UserProfileCache.instance == null) {
    144           UserProfileCache.instance = new UserProfileCache();
    145         }
    146         return UserProfileCache.instance;
    147       }
    148     }
    149 
    150     @Override
    151     protected CacheAccess<String, UserProfile> createNewCache() throws IOException {
    152       CacheAccess<String, UserProfile> cache = JCSCacheManager.getCache("userProfile", 100, 1000,
    153           Caches.getCacheDirectory().getPath());
    154       IElementAttributes atts = cache.getDefaultElementAttributes();
    155       atts.setMaxLife(604_800_000); // Sets lifetime to 7 days (604800000=1000*60*60*24*7)
    156       cache.setDefaultElementAttributes(atts);
    157       return cache;
    158     }
    159   }
    160137}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cache/StreetsideCache.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside.cache;
    33
     4import java.io.UncheckedIOException;
     5import java.net.MalformedURLException;
     6import java.net.URI;
    47import java.net.URL;
    58import java.util.HashMap;
     
    811import org.openstreetmap.josm.data.cache.JCSCachedTileLoaderJob;
    912import org.openstreetmap.josm.data.imagery.TileJobOptions;
    10 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL.VirtualEarth;
    1113
    1214/**
     
    1820public class StreetsideCache extends JCSCachedTileLoaderJob<String, BufferedImageCacheEntry> {
    1921
    20   private final URL url;
    21   private final String id;
     22    private final String url;
    2223
    23   /**
    24    * Main constructor.
    25    *
    26    * @param id   The id of the image.
    27    * @param type The type of image that must be downloaded (THUMBNAIL or
    28    *       FULL_IMAGE).
    29    */
    30   public StreetsideCache(final String id, final Type type) {
    31     super(Caches.ImageCache.getInstance().getCache(), new TileJobOptions(50000, 50000, new HashMap<>(), 50000L));
     24    /**
     25     * Main constructor.
     26     *
     27     * @param url The image URL
     28     */
     29    public StreetsideCache(final String url) {
     30        super(Caches.ImageCache.getInstance().getCache(), new TileJobOptions(50000, 50000, new HashMap<>(), 50000L));
     31        this.url = url;
     32    }
    3233
    33     if (id == null || type == null) {
    34       this.id = null;
    35       url = null;
    36     } else {
    37       this.id = id;
    38       url = VirtualEarth.streetsideTile(id, type == Type.THUMBNAIL);
     34    @Override
     35    public String getCacheKey() {
     36        return this.url;
    3937    }
    40   }
    4138
    42   @Override
    43   public String getCacheKey() {
    44     return id;
    45   }
     39    @Override
     40    public URL getUrl() {
     41        try {
     42            return URI.create(this.url).toURL();
     43        } catch (MalformedURLException e) {
     44            throw new UncheckedIOException(e);
     45        }
     46    }
    4647
    47   @Override
    48   public URL getUrl() {
    49     return url;
    50   }
     48    @Override
     49    protected BufferedImageCacheEntry createCacheEntry(byte[] content) {
     50        return new BufferedImageCacheEntry(content);
     51    }
    5152
    52   @Override
    53   protected BufferedImageCacheEntry createCacheEntry(byte[] content) {
    54     return new BufferedImageCacheEntry(content);
    55   }
    56 
    57   @Override
    58   protected boolean isObjectLoadable() {
    59     if (cacheData == null) {
    60       return false;
     53    @Override
     54    protected boolean isObjectLoadable() {
     55        if (cacheData == null) {
     56            return false;
     57        }
     58        final byte[] content = cacheData.getContent();
     59        return content != null && content.length > 0;
    6160    }
    62     final byte[] content = cacheData.getContent();
    63     return content != null && content.length > 0;
    64   }
    65 
    66   /**
    67    * Types of images.
    68    *
    69    * @author nokutu
    70    */
    71   public enum Type {
    72     /**
    73      * Full quality image
    74      */
    75     FULL_IMAGE,
    76     /**
    77      * Low quality image
    78      */
    79     THUMBNAIL
    80   }
    8161}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cubemap/CameraTransformer.java

    r36194 r36228  
    77import javafx.scene.transform.Translate;
    88
    9 // necessary because JavaFX is not an official part of Java 8 (access)
    10 @SuppressWarnings("restriction")
     9/**
     10 * A transformer for the camera rotations
     11 */
    1112public class CameraTransformer extends Group {
    1213
    13   public Translate t = new Translate();
    14   public Translate p = new Translate();
    15   public Translate ip = new Translate();
    16   public Rotate rx = new Rotate();
    17   public Rotate ry = new Rotate();
    18   public Rotate rz = new Rotate();
    19   public Scale s = new Scale();
     14    public final Translate t = new Translate();
     15    private final Translate p = new Translate();
     16    private final Translate ip = new Translate();
     17    public final Rotate rx = new Rotate();
     18    public final Rotate ry = new Rotate();
     19    private final Rotate rz = new Rotate();
     20    private final Scale s = new Scale();
    2021
    21   {
    22     rx.setAxis(Rotate.X_AXIS);
    23   }
     22    /**
     23     * Create a new transformer
     24     */
     25    public CameraTransformer() {
     26        super();
     27        rx.setAxis(Rotate.X_AXIS);
     28        ry.setAxis(Rotate.Y_AXIS);
     29        rz.setAxis(Rotate.Z_AXIS);
     30        getTransforms().addAll(t, rz, ry, rx, s);
     31    }
    2432
    25   {
    26     ry.setAxis(Rotate.Y_AXIS);
    27   }
     33    /**
     34     * Create a new transformer with a specific rotation
     35     * @param rotateOrder The order in which rotations will occur
     36     */
     37    public CameraTransformer(CameraTransformer.RotateOrder rotateOrder) {
     38        super();
     39        switch (rotateOrder) {
     40        case XYZ:
     41            getTransforms().addAll(t, p, rz, ry, rx, s, ip);
     42            break;
     43        case XZY:
     44            getTransforms().addAll(t, p, ry, rz, rx, s, ip);
     45            break;
     46        case YXZ:
     47            getTransforms().addAll(t, p, rz, rx, ry, s, ip);
     48            break;
     49        case YZX:
     50            getTransforms().addAll(t, p, rx, rz, ry, s, ip); // For Camera
     51            break;
     52        case ZXY:
     53            getTransforms().addAll(t, p, ry, rx, rz, s, ip);
     54            break;
     55        case ZYX:
     56            getTransforms().addAll(t, p, rx, ry, rz, s, ip);
     57            break;
     58        }
     59    }
    2860
    29   {
    30     rz.setAxis(Rotate.Z_AXIS);
    31   }
     61    public void setTranslate(double x, double y, double z) {
     62        t.setX(x);
     63        t.setY(y);
     64        t.setZ(z);
     65    }
    3266
    33   public CameraTransformer() {
    34     super();
    35     getTransforms().addAll(t, rz, ry, rx, s);
    36   }
     67    public void setTranslate(double x, double y) {
     68        t.setX(x);
     69        t.setY(y);
     70    }
    3771
    38   public CameraTransformer(CameraTransformer.RotateOrder rotateOrder) {
    39     super();
    40     switch (rotateOrder) {
    41     case XYZ:
    42       getTransforms().addAll(t, p, rz, ry, rx, s, ip);
    43       break;
    44     case XZY:
    45       getTransforms().addAll(t, p, ry, rz, rx, s, ip);
    46       break;
    47     case YXZ:
    48       getTransforms().addAll(t, p, rz, rx, ry, s, ip);
    49       break;
    50     case YZX:
    51       getTransforms().addAll(t, p, rx, rz, ry, s, ip); // For Camera
    52       break;
    53     case ZXY:
    54       getTransforms().addAll(t, p, ry, rx, rz, s, ip);
    55       break;
    56     case ZYX:
    57       getTransforms().addAll(t, p, rx, ry, rz, s, ip);
    58       break;
     72    public void setTx(double x) {
     73        t.setX(x);
    5974    }
    60   }
    6175
    62   public void setTranslate(double x, double y, double z) {
    63     t.setX(x);
    64     t.setY(y);
    65     t.setZ(z);
    66   }
     76    public void setTy(double y) {
     77        t.setY(y);
     78    }
    6779
    68   public void setTranslate(double x, double y) {
    69     t.setX(x);
    70     t.setY(y);
    71   }
     80    public void setTz(double z) {
     81        t.setZ(z);
     82    }
    7283
    73   public void setTx(double x) {
    74     t.setX(x);
    75   }
     84    public void setRotate(double x, double y, double z) {
     85        rx.setAngle(x);
     86        ry.setAngle(y);
     87        rz.setAngle(z);
     88    }
    7689
    77   public void setTy(double y) {
    78     t.setY(y);
    79   }
     90    public void setRotateX(double x) {
     91        rx.setAngle(x);
     92    }
    8093
    81   public void setTz(double z) {
    82     t.setZ(z);
    83   }
     94    public void setRotateY(double y) {
     95        ry.setAngle(y);
     96    }
    8497
    85   public void setRotate(double x, double y, double z) {
    86     rx.setAngle(x);
    87     ry.setAngle(y);
    88     rz.setAngle(z);
    89   }
     98    public void setRotateZ(double z) {
     99        rz.setAngle(z);
     100    }
    90101
    91   public void setRotateX(double x) {
    92     rx.setAngle(x);
    93   }
     102    public void setRx(double x) {
     103        rx.setAngle(x);
     104    }
    94105
    95   public void setRotateY(double y) {
    96     ry.setAngle(y);
    97   }
     106    public void setRy(double y) {
     107        ry.setAngle(y);
     108    }
    98109
    99   public void setRotateZ(double z) {
    100     rz.setAngle(z);
    101   }
     110    public void setRz(double z) {
     111        rz.setAngle(z);
     112    }
    102113
    103   public void setRx(double x) {
    104     rx.setAngle(x);
    105   }
     114    public void setScale(double scaleFactor) {
     115        s.setX(scaleFactor);
     116        s.setY(scaleFactor);
     117        s.setZ(scaleFactor);
     118    }
    106119
    107   public void setRy(double y) {
    108     ry.setAngle(y);
    109   }
     120    public void setScale(double x, double y, double z) {
     121        s.setX(x);
     122        s.setY(y);
     123        s.setZ(z);
     124    }
    110125
    111   public void setRz(double z) {
    112     rz.setAngle(z);
    113   }
     126    public void setSx(double x) {
     127        s.setX(x);
     128    }
    114129
    115   public void setScale(double scaleFactor) {
    116     s.setX(scaleFactor);
    117     s.setY(scaleFactor);
    118     s.setZ(scaleFactor);
    119   }
     130    public void setSy(double y) {
     131        s.setY(y);
     132    }
    120133
    121   public void setScale(double x, double y, double z) {
    122     s.setX(x);
    123     s.setY(y);
    124     s.setZ(z);
    125   }
     134    public void setSz(double z) {
     135        s.setZ(z);
     136    }
    126137
    127   public void setSx(double x) {
    128     s.setX(x);
    129   }
     138    public void setPivot(double x, double y, double z) {
     139        p.setX(x);
     140        p.setY(y);
     141        p.setZ(z);
     142        ip.setX(-x);
     143        ip.setY(-y);
     144        ip.setZ(-z);
     145    }
    130146
    131   public void setSy(double y) {
    132     s.setY(y);
    133   }
     147    public void reset() {
     148        rx.setAngle(0.0);
     149        ry.setAngle(0.0);
     150        rz.setAngle(0.0);
     151        resetTSP();
     152    }
    134153
    135   public void setSz(double z) {
    136     s.setZ(z);
    137   }
     154    public void resetTSP() {
     155        t.setX(0.0);
     156        t.setY(0.0);
     157        t.setZ(0.0);
     158        s.setX(1.0);
     159        s.setY(1.0);
     160        s.setZ(1.0);
     161        p.setX(0.0);
     162        p.setY(0.0);
     163        p.setZ(0.0);
     164        ip.setX(0.0);
     165        ip.setY(0.0);
     166        ip.setZ(0.0);
     167    }
    138168
    139   public void setPivot(double x, double y, double z) {
    140     p.setX(x);
    141     p.setY(y);
    142     p.setZ(z);
    143     ip.setX(-x);
    144     ip.setY(-y);
    145     ip.setZ(-z);
    146   }
    147 
    148   public void reset() {
    149     t.setX(0.0);
    150     t.setY(0.0);
    151     t.setZ(0.0);
    152     rx.setAngle(0.0);
    153     ry.setAngle(0.0);
    154     rz.setAngle(0.0);
    155     s.setX(1.0);
    156     s.setY(1.0);
    157     s.setZ(1.0);
    158     p.setX(0.0);
    159     p.setY(0.0);
    160     p.setZ(0.0);
    161     ip.setX(0.0);
    162     ip.setY(0.0);
    163     ip.setZ(0.0);
    164   }
    165 
    166   public void resetTSP() {
    167     t.setX(0.0);
    168     t.setY(0.0);
    169     t.setZ(0.0);
    170     s.setX(1.0);
    171     s.setY(1.0);
    172     s.setZ(1.0);
    173     p.setX(0.0);
    174     p.setY(0.0);
    175     p.setZ(0.0);
    176     ip.setX(0.0);
    177     ip.setY(0.0);
    178     ip.setZ(0.0);
    179   }
    180 
    181   public enum RotateOrder {
    182     XYZ, XZY, YXZ, YZX, ZXY, ZYX
    183   }
     169    public enum RotateOrder {
     170        XYZ, XZY, YXZ, YZX, ZXY, ZYX
     171    }
    184172}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cubemap/CubemapBox.java

    r36194 r36228  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.plugins.streetside.cubemap;
    3 
    4 import java.awt.image.BufferedImage;
    53
    64import org.openstreetmap.josm.plugins.streetside.utils.GraphicsUtils;
     
    97import javafx.beans.property.DoubleProperty;
    108import javafx.beans.property.SimpleDoubleProperty;
    11 import javafx.geometry.Rectangle2D;
    129import javafx.scene.Group;
    1310import javafx.scene.PerspectiveCamera;
     
    1916
    2017/**
     18 * A box for showing the cubemap images
    2119 * @author renerr18
    2220 */
    23 @SuppressWarnings("restriction")
    2421public class CubemapBox extends Group {
    2522
    26   private final Affine affine = new Affine();
    27   private final ImageView front = new ImageView();
    28   private final ImageView right = new ImageView();
    29   private final ImageView back = new ImageView();
    30   private final ImageView left = new ImageView();
    31   private final ImageView up = new ImageView();
    32   private final ImageView down = new ImageView();
    33   private final ImageView[] views = new ImageView[] { front, right, back, left, up, down };
    34   private final Image frontImg;
    35   private final Image rightImg;
    36   private final Image backImg;
    37   private final Image leftImg;
    38   private final Image upImg;
    39   private final Image downImg;
    40   private final PerspectiveCamera camera;
    41   private final CubemapBoxImageType imageType;
    42   private Image singleImg;
    43   private AnimationTimer timer;
     23    private final Affine affine = new Affine();
     24    private final ImageView front = new ImageView();
     25    private final ImageView right = new ImageView();
     26    private final ImageView back = new ImageView();
     27    private final ImageView left = new ImageView();
     28    private final ImageView up = new ImageView();
     29    private final ImageView down = new ImageView();
     30    private final ImageView[] views = new ImageView[] { front, right, back, left, up, down };
     31    private final Image frontImg;
     32    private final Image rightImg;
     33    private final Image backImg;
     34    private final Image leftImg;
     35    private final Image upImg;
     36    private final Image downImg;
     37    private final PerspectiveCamera camera;
    4438
    45   {
    46     front.setId(CubemapUtils.CubemapFaces.FRONT.getValue());
    47     right.setId(CubemapUtils.CubemapFaces.RIGHT.getValue());
    48     back.setId(CubemapUtils.CubemapFaces.BACK.getValue());
    49     left.setId(CubemapUtils.CubemapFaces.LEFT.getValue());
    50     up.setId(CubemapUtils.CubemapFaces.UP.getValue());
    51     down.setId(CubemapUtils.CubemapFaces.DOWN.getValue());
     39    /**
     40     * Create a new CubemapBox
     41     * @param frontImg The front image
     42     * @param rightImg The right image
     43     * @param backImg The back image
     44     * @param leftImg The left image
     45     * @param upImg The up image
     46     * @param downImg The down image
     47     * @param size The size of each cube side
     48     * @param camera The camera to use
     49     */
     50    public CubemapBox(Image frontImg, Image rightImg, Image backImg, Image leftImg, Image upImg, Image downImg,
     51            double size, PerspectiveCamera camera) {
     52        super();
    5253
    53   }
     54        this.front.setId(CubemapUtils.CubemapFaces.FRONT.getValue());
     55        this.right.setId(CubemapUtils.CubemapFaces.RIGHT.getValue());
     56        this.back.setId(CubemapUtils.CubemapFaces.BACK.getValue());
     57        this.left.setId(CubemapUtils.CubemapFaces.LEFT.getValue());
     58        this.up.setId(CubemapUtils.CubemapFaces.UP.getValue());
     59        this.down.setId(CubemapUtils.CubemapFaces.DOWN.getValue());
    5460
    55   public CubemapBox(Image frontImg, Image rightImg, Image backImg, Image leftImg, Image upImg, Image downImg,
    56       double size, PerspectiveCamera camera) {
     61        this.frontImg = frontImg;
     62        this.rightImg = rightImg;
     63        this.backImg = backImg;
     64        this.leftImg = leftImg;
     65        this.upImg = upImg;
     66        this.downImg = downImg;
     67        this.size.set(size);
     68        this.camera = camera;
    5769
    58     super();
     70        loadImageViews();
    5971
    60     imageType = CubemapBoxImageType.MULTIPLE;
     72        getTransforms().add(affine);
    6173
    62     this.frontImg = frontImg;
    63     this.rightImg = rightImg;
    64     this.backImg = backImg;
    65     this.leftImg = leftImg;
    66     this.upImg = upImg;
    67     this.downImg = downImg;
    68     this.size.set(size);
    69     this.camera = camera;
     74        getChildren().addAll(views);
    7075
    71     loadImageViews();
    72 
    73     getTransforms().add(affine);
    74 
    75     getChildren().addAll(views);
    76 
    77     startTimer();
    78   }
    79 
    80   public void loadImageViews() {
    81 
    82     for (ImageView iv : views) {
    83       iv.setSmooth(true);
    84       iv.setPreserveRatio(true);
     76        startTimer();
    8577    }
    8678
    87     validateImageType();
    88   }
     79    /**
     80     * Load the image views
     81     */
     82    public void loadImageViews() {
    8983
    90   private void layoutViews() {
     84        for (ImageView iv : views) {
     85            iv.setSmooth(true);
     86            iv.setPreserveRatio(true);
     87        }
    9188
    92     for (ImageView v : views) {
    93       v.setFitWidth(getSize());
    94       v.setFitHeight(getSize());
     89        validateImageType();
    9590    }
    9691
    97     back.setTranslateX(-0.5 * getSize());
    98     back.setTranslateY(-0.5 * getSize());
    99     back.setTranslateZ(-0.5 * getSize());
     92    private void layoutViews() {
    10093
    101     front.setTranslateX(-0.5 * getSize());
    102     front.setTranslateY(-0.5 * getSize());
    103     front.setTranslateZ(0.5 * getSize());
    104     front.setRotationAxis(Rotate.Z_AXIS);
    105     front.setRotate(-180);
    106     front.getTransforms().add(new Rotate(180, front.getFitHeight() / 2, 0, 0, Rotate.X_AXIS));
    107     front.setTranslateY(front.getTranslateY() - getSize());
     94        for (ImageView v : views) {
     95            v.setFitWidth(getSize());
     96            v.setFitHeight(getSize());
     97        }
    10898
    109     up.setTranslateX(-0.5 * getSize());
    110     up.setTranslateY(-1 * getSize());
    111     up.setRotationAxis(Rotate.X_AXIS);
    112     up.setRotate(-90);
     99        back.setTranslateX(-0.5 * getSize());
     100        back.setTranslateY(-0.5 * getSize());
     101        back.setTranslateZ(-0.5 * getSize());
    113102
    114     down.setTranslateX(-0.5 * getSize());
    115     down.setTranslateY(0);
    116     down.setRotationAxis(Rotate.X_AXIS);
    117     down.setRotate(90);
     103        front.setTranslateX(-0.5 * getSize());
     104        front.setTranslateY(-0.5 * getSize());
     105        front.setTranslateZ(0.5 * getSize());
     106        front.setRotationAxis(Rotate.Z_AXIS);
     107        front.setRotate(-180);
     108        front.getTransforms().add(new Rotate(180, front.getFitHeight() / 2, 0, 0, Rotate.X_AXIS));
     109        front.setTranslateY(front.getTranslateY() - getSize());
    118110
    119     left.setTranslateX(-1 * getSize());
    120     left.setTranslateY(-0.5 * getSize());
    121     left.setRotationAxis(Rotate.Y_AXIS);
    122     left.setRotate(90);
     111        up.setTranslateX(-0.5 * getSize());
     112        up.setTranslateY(-1 * getSize());
     113        up.setRotationAxis(Rotate.X_AXIS);
     114        up.setRotate(-90);
    123115
    124     right.setTranslateX(0);
    125     right.setTranslateY(-0.5 * getSize());
    126     right.setRotationAxis(Rotate.Y_AXIS);
    127     right.setRotate(-90);
     116        down.setTranslateX(-0.5 * getSize());
     117        down.setTranslateY(0);
     118        down.setRotationAxis(Rotate.X_AXIS);
     119        down.setRotate(90);
    128120
    129   }
     121        left.setTranslateX(-1 * getSize());
     122        left.setTranslateY(-0.5 * getSize());
     123        left.setRotationAxis(Rotate.Y_AXIS);
     124        left.setRotate(90);
    130125
    131   /**
    132    * for single image creates viewports and sets all views(image) to singleImg for multiple... sets images per view.
    133    */
    134   private void validateImageType() {
    135     switch (imageType) {
    136     case SINGLE:
    137       loadSingleImageViewports();
    138       break;
    139     case MULTIPLE:
    140       setMultipleImages();
    141       break;
     126        right.setTranslateX(0);
     127        right.setTranslateY(-0.5 * getSize());
     128        right.setRotationAxis(Rotate.Y_AXIS);
     129        right.setRotate(-90);
    142130    }
    143   }
    144131
    145   private void loadSingleImageViewports() {
    146     layoutViews();
    147     double width = singleImg.getWidth();
    148     double height = singleImg.getHeight();
     132    /**
     133     * for single image creates viewports and sets all views(image) to singleImg for multiple... sets images per view.
     134     */
     135    private void validateImageType() {
     136        setMultipleImages();
     137    }
    149138
    150     // simple check to see if cells will be square
    151     if (width / 4 != height / 3) {
    152       throw new UnsupportedOperationException("Image does not comply with size constraints");
     139    private void setMultipleImages() {
     140        GraphicsUtils.PlatformHelper.run(() -> {
     141            layoutViews();
     142            front.setImage(frontImg);
     143            right.setImage(rightImg);
     144            back.setImage(backImg);
     145            left.setImage(leftImg);
     146            up.setImage(upImg);
     147            down.setImage(downImg);
     148
     149        });
    153150    }
    154     double cellSize = singleImg.getWidth() - singleImg.getHeight();
    155151
    156     recalculateSize(cellSize);
     152    /**
     153     * Start the UI timer for updates
     154     */
     155    public void startTimer() {
     156        AnimationTimer timer = new AnimationTimer() {
     157            @Override
     158            public void handle(long now) {
     159                Transform ct = (camera != null) ? camera.getLocalToSceneTransform() : null;
     160                if (ct != null) {
     161                    affine.setTx(ct.getTx());
     162                    affine.setTy(ct.getTy());
     163                    affine.setTz(ct.getTz());
     164                }
     165            }
     166        };
     167        timer.start();
     168    }
    157169
    158     double topx = cellSize;
    159     double topy = 0;
    160     double botx = cellSize;
    161     double boty = cellSize * 2;
    162     double leftx = 0;
    163     double lefty = cellSize;
    164     double rightx = cellSize * 2;
    165     double righty = cellSize;
    166     double fwdx = cellSize;
    167     double fwdy = cellSize;
    168     double backx = cellSize * 3;
    169     double backy = cellSize;
     170    public final double getSize() {
     171        return size.get();
     172    }
    170173
    171     // add top padding x+, y+, width-, height
    172     up.setViewport(new Rectangle2D(topx, topy, cellSize, cellSize));
     174    private final DoubleProperty size = new SimpleDoubleProperty();
    173175
    174     // add left padding x, y+, width, height-
    175     left.setViewport(new Rectangle2D(leftx, lefty, cellSize - 1, cellSize - 1));
    176 
    177     // add front padding x+, y+, width-, height
    178     front.setViewport(new Rectangle2D(fwdx, fwdy, cellSize, cellSize));
    179 
    180     // add right padding x, y+, width, height-
    181     right.setViewport(new Rectangle2D(rightx, righty, cellSize, cellSize));
    182 
    183     // add back padding x, y+, width, height-
    184     back.setViewport(new Rectangle2D(backx + 1, backy - 1, cellSize - 1, cellSize - 1));
    185 
    186     // add bottom padding x+, y, width-, height-
    187     down.setViewport(new Rectangle2D(botx, boty, cellSize, cellSize));
    188 
    189     for (ImageView v : views) {
    190       v.setImage(singleImg);
     176    public ImageView[] getViews() {
     177        return views;
    191178    }
    192   }
    193 
    194   private void recalculateSize(double cell) {
    195     double factor = Math.floor(getSize() / cell);
    196     setSize(cell * factor);
    197   }
    198 
    199   public synchronized void setImage(BufferedImage img, int position) {
    200     views[position].setImage(GraphicsUtils.convertBufferedImage2JavaFXImage(img));
    201 
    202   }
    203 
    204   private void setMultipleImages() {
    205     GraphicsUtils.PlatformHelper.run(() -> {
    206       layoutViews();
    207       front.setImage(frontImg);
    208       right.setImage(rightImg);
    209       back.setImage(backImg);
    210       left.setImage(leftImg);
    211       up.setImage(upImg);
    212       down.setImage(downImg);
    213 
    214     });
    215   }
    216 
    217   public void startTimer() {
    218     timer = new AnimationTimer() {
    219       @Override
    220       public void handle(long now) {
    221         Transform ct = (camera != null) ? camera.getLocalToSceneTransform() : null;
    222         if (ct != null) {
    223           affine.setTx(ct.getTx());
    224           affine.setTy(ct.getTy());
    225           affine.setTz(ct.getTz());
    226         }
    227       }
    228     };
    229     timer.start();
    230   }
    231 
    232   public final double getSize() {
    233     return size.get();
    234   }
    235 
    236   public final void setSize(double value) {
    237     size.set(value);
    238   } /*
    239     * Properties
    240     */
    241 
    242   private final DoubleProperty size = new SimpleDoubleProperty() {
    243     @Override
    244     protected void invalidated() {
    245       switch (imageType) {
    246       case SINGLE:
    247         layoutViews();
    248         break;
    249       case MULTIPLE:
    250         break;
    251       }
    252 
    253     }
    254   };
    255 
    256   public DoubleProperty sizeProperty() {
    257     return size;
    258   }
    259 
    260   public ImageView[] getViews() {
    261     return views;
    262   }
    263 
    264   public enum CubemapBoxImageType {
    265     MULTIPLE, SINGLE
    266   }
    267 
    268179}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cubemap/CubemapBuilder.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside.cubemap;
    33
     4import java.awt.geom.AffineTransform;
     5import java.awt.image.AffineTransformOp;
    46import java.awt.image.BufferedImage;
    57import java.text.MessageFormat;
    68import java.util.ArrayList;
     9import java.util.Collections;
    710import java.util.EnumSet;
    8 import java.util.HashMap;
    911import java.util.List;
    1012import java.util.Map;
     
    1517import java.util.concurrent.Executors;
    1618import java.util.concurrent.Future;
     19import java.util.concurrent.TimeUnit;
     20import java.util.concurrent.atomic.AtomicInteger;
    1721import java.util.logging.Logger;
     22import java.util.stream.Collectors;
    1823
    1924import org.openstreetmap.josm.gui.MainApplication;
     25import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY;
    2026import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    21 import org.openstreetmap.josm.plugins.streetside.StreetsideCubemap;
    2227import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener;
     28import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
     29import org.openstreetmap.josm.plugins.streetside.cache.StreetsideCache;
    2330import org.openstreetmap.josm.plugins.streetside.gui.StreetsideViewerDialog;
    2431import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.StreetsideViewerPanel;
    25 import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.ThreeSixtyDegreeViewerPanel;
    2632import org.openstreetmap.josm.plugins.streetside.utils.GraphicsUtils;
    2733import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
    2834import org.openstreetmap.josm.tools.Logging;
    2935
     36import javafx.embed.swing.SwingFXUtils;
    3037import javafx.scene.image.Image;
    31 import javafx.scene.image.ImageView;
    32 
     38
     39/**
     40 * Build a cubemap
     41 */
    3342// JavaFX access in Java 8
    34 public class CubemapBuilder implements ITileDownloadingTaskListener, StreetsideDataListener {
    35 
    36   private static final Logger LOGGER = Logger.getLogger(CubemapBuilder.class.getCanonicalName());
    37 
    38   private static CubemapBuilder instance;
    39   protected boolean isBuilding;
    40   private StreetsideCubemap cubemap;
    41   private long startTime;
    42 
    43   private Map<String, BufferedImage> tileImages = new ConcurrentHashMap<>();
    44   private ExecutorService pool;
    45 
    46   private int currentTileCount = 0;
    47 
    48   private CubemapBuilder() {
    49     // private constructor to avoid instantiation
    50   }
    51 
    52   public static CubemapBuilder getInstance() {
    53     if (instance == null) {
    54       instance = new CubemapBuilder();
    55     }
    56     return instance;
    57   }
    58 
    59   /**
    60    * @return true, iff the singleton instance is present
    61    */
    62   public static boolean hasInstance() {
    63     return CubemapBuilder.instance != null;
    64   }
    65 
    66   /**
    67    * Destroys the unique instance of the class.
    68    */
    69   public static synchronized void destroyInstance() {
    70     CubemapBuilder.instance = null;
    71   }
    72 
    73   /**
    74    * @return the tileImages
    75    */
    76   public Map<String, BufferedImage> getTileImages() {
    77     return tileImages;
    78   }
    79 
    80   /**
    81    * @param tileImages the tileImages to set
    82    */
    83   public void setTileImages(Map<String, BufferedImage> tileImages) {
    84     this.tileImages = tileImages;
    85   }
    86 
    87   /**
    88    * Fired when any image is added to the database.
    89    */
    90   @Override
    91   public void imagesAdded() {
    92     // Not implemented by the CubemapBuilder
    93   }
    94 
    95   /**
    96    * Fired when the selected image is changed by something different from
    97    * manually clicking on the icon.
    98    *
    99    * @param oldImage Old selected {@link StreetsideAbstractImage}
    100    * @param newImage New selected {@link StreetsideAbstractImage}
    101    * @see StreetsideDataListener
    102    */
    103   @Override
    104   public void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) {
    105 
    106     startTime = System.currentTimeMillis();
    107 
    108     if (newImage != null) {
    109 
    110       cubemap = null;
    111       cubemap = new StreetsideCubemap(newImage.getId(), newImage.getLatLon(), newImage.getHe());
    112       currentTileCount = 0;
    113       resetTileImages();
    114 
    115       // download cubemap images in different threads and then subsequently
    116       // set the cubeface images in JavaFX
    117       downloadCubemapImages(cubemap.getId());
    118 
    119       long runTime = (System.currentTimeMillis() - startTime) / 1000;
    120       if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    121         LOGGER.log(Logging.LEVEL_DEBUG, MessageFormat
    122             .format("Completed downloading tiles for {0} in {1} seconds.", newImage.getId(), runTime));
    123       }
    124     }
    125   }
    126 
    127   public void reload(String imageId) {
    128     if (cubemap != null && imageId.equals(cubemap.getId())) {
    129       tileImages = new HashMap<>();
    130       downloadCubemapImages(imageId);
    131     }
    132   }
    133 
    134   public void downloadCubemapImages(String imageId) {
    135     ThreeSixtyDegreeViewerPanel panel360 = StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel();
    136     if (panel360 != null && panel360.getScene() != panel360.getLoadingScene()) {
    137       panel360.setScene(panel360.getLoadingScene());
    138     }
    139 
    140     final int maxThreadCount = Boolean.TRUE.equals(StreetsideProperties.DOWNLOAD_CUBEFACE_TILES_TOGETHER.get()) ? 6
    141         : 6 * CubemapUtils.getMaxCols() * CubemapUtils.getMaxRows();
    142 
    143     int fails = 0;
    144 
    145     // TODO: message for progress bar
    146     String[] message = new String[2];
    147     message[0] = MessageFormat.format("Downloading Streetside imagery for {0}", imageId);
    148     message[1] = "Wait for completion…….";
    149 
    150     long startTime = System.currentTimeMillis();
    151 
    152     if (!CubemapBuilder.getInstance().getTileImages().keySet().isEmpty()) {
    153       pool.shutdownNow();
    154       CubemapBuilder.getInstance().resetTileImages();
    155     }
    156 
    157     try {
    158 
    159       pool = Executors.newFixedThreadPool(maxThreadCount);
    160       List<Callable<List<String>>> tasks = new ArrayList<>(maxThreadCount);
    161 
    162       if (Boolean.TRUE.equals(StreetsideProperties.DOWNLOAD_CUBEFACE_TILES_TOGETHER.get())) {
    163         EnumSet.allOf(CubemapUtils.CubemapFaces.class).forEach(face -> {
    164           String tileId = imageId + face.getValue();
    165           tasks.add(new TileDownloadingTask(tileId));
     43public final class CubemapBuilder implements ITileDownloadingTaskListener, StreetsideDataListener {
     44
     45    private static final Logger LOGGER = Logger.getLogger(CubemapBuilder.class.getCanonicalName());
     46
     47    private static CubemapBuilder instance;
     48    boolean isBuilding;
     49    private StreetsideAbstractImage cubemap;
     50    private long startTime;
     51
     52    private final Map<CubeMapTileXY, BufferedImage> tileImages = new ConcurrentHashMap<>();
     53    private final ExecutorService pool = Executors.newVirtualThreadPerTaskExecutor();
     54    private final List<Future<?>> lastFutures = new ArrayList<>();
     55
     56    private final AtomicInteger currentTileCount = new AtomicInteger();
     57
     58    private CubemapBuilder() {
     59        // private constructor to avoid instantiation
     60    }
     61
     62    public static synchronized CubemapBuilder getInstance() {
     63        if (instance == null) {
     64            instance = new CubemapBuilder();
     65        }
     66        return instance;
     67    }
     68
     69    /**
     70     * Destroys the unique instance of the class.
     71     */
     72    public static synchronized void destroyInstance() {
     73        CubemapBuilder.instance = null;
     74    }
     75
     76    /**
     77     * Get the current tile images
     78     * @return the tileImages
     79     */
     80    public Map<CubeMapTileXY, BufferedImage> getTileImages() {
     81        return tileImages;
     82    }
     83
     84    /**
     85     * Set the tile images to show
     86     * @param tileImages the tileImages to set
     87     */
     88    public void setTileImages(Map<CubeMapTileXY, BufferedImage> tileImages) {
     89        synchronized (this.tileImages) {
     90            this.tileImages.clear();
     91            this.tileImages.putAll(tileImages);
     92        }
     93    }
     94
     95    /**
     96     * Add an entry to the tile images
     97     * @param key The key to use
     98     * @param value The value to use
     99     */
     100    private void addTileImage(CubeMapTileXY key, BufferedImage value) {
     101        synchronized (this.tileImages) {
     102            this.tileImages.put(key, value);
     103        }
     104    }
     105
     106    /**
     107     * Fired when any image is added to the database.
     108     */
     109    @Override
     110    public void imagesAdded() {
     111        // Not implemented by the CubemapBuilder
     112    }
     113
     114    /**
     115     * Fired when the selected image is changed by something different from
     116     * manually clicking on the icon.
     117     *
     118     * @param oldImage Old selected {@link StreetsideImage}
     119     * @param newImage New selected {@link StreetsideImage}
     120     * @see StreetsideDataListener
     121     */
     122    @Override
     123    public void selectedImageChanged(StreetsideImage oldImage, StreetsideImage newImage) {
     124
     125        startTime = System.currentTimeMillis();
     126
     127        if (newImage != null) {
     128
     129            cubemap = newImage;
     130            currentTileCount.set(0);
     131            resetTileImages();
     132
     133            // download cubemap images in different threads and then subsequently
     134            // set the cubeface images in JavaFX
     135            downloadCubemapImages(cubemap);
     136
     137            long runTime = (System.currentTimeMillis() - startTime) / 1000;
     138            if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     139                LOGGER.log(Logging.LEVEL_DEBUG, "Completed downloading tiles for {0} in {1} seconds.",
     140                        new Object[] { newImage.id(), runTime });
     141            }
     142        }
     143    }
     144
     145    /**
     146     * Reload an image
     147     * @param image The image to reload -- nothing happens if it is not the current image
     148     */
     149    public void reload(StreetsideAbstractImage image) {
     150        if (cubemap != null && image.id().equals(cubemap.id())) {
     151            this.tileImages.clear();
     152            downloadCubemapImages(image);
     153        }
     154    }
     155
     156    /**
     157     * Download the cubemap images for the specified image
     158     * @param image The streetside image to get the cubemap for
     159     * @return The images (FIXME: Currently returns an empty map)
     160     */
     161    public Map<CubeMapTileXY, StreetsideCache> downloadCubemapImages(StreetsideAbstractImage image) {
     162        final var panel360 = StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel();
     163        if (panel360 != null && panel360.getScene() != panel360.getLoadingScene()) {
     164            panel360.setScene(panel360.getLoadingScene());
     165        }
     166
     167        final int maxThreadCount = CubemapUtils.NUM_SIDES * CubemapUtils.getMaxCols(image)
     168                * CubemapUtils.getMaxRows(image);
     169
     170        // TODO: message for progress bar
     171        // final var message = new String[2];
     172        // message[0] = MessageFormat.format("Downloading Streetside imagery for {0}", image.id());
     173        // message[1] = "Wait for completion…….";
     174
     175        final long startTimeDownloadCubemapImages = System.currentTimeMillis();
     176
     177        if (!CubemapBuilder.getInstance().getTileImages().keySet().isEmpty()) {
     178            CubemapBuilder.getInstance().resetTileImages();
     179        }
     180
     181        List<Callable<List<String>>> tasks = new ArrayList<>(maxThreadCount);
     182
     183        if (Boolean.TRUE.equals(StreetsideProperties.DOWNLOAD_CUBEFACE_TILES_TOGETHER.get())) {
     184            EnumSet.allOf(CubemapUtils.CubemapFaces.class)
     185                    .forEach(face -> tasks.add(new TileDownloadingTask(image, face, new CubeMapTileXY(face, 0, 0))));
     186        } else {
     187            final var zoom = Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())
     188                    // launch 16-tiled (high-res) downloading tasks
     189                    ? image.zoomMax()
     190                    // launch 4-tiled (low-res) downloading tasks . . .
     191                    : image.zoomMin();
     192            // download all imagery for each cubeface at once
     193            for (var face : CubemapUtils.CubemapFaces.values()) {
     194                tasks.addAll(
     195                        image.getFaceTiles(face, zoom).map(f -> new TileDownloadingTask(image, face, f.a)).toList());
     196            }
     197        }
     198        // finish preparing tasks for invocation
     199
     200        // execute tasks
     201        MainApplication.worker.submit(() -> {
     202            try {
     203                final List<Future<List<String>>> results;
     204                synchronized (lastFutures) {
     205                    lastFutures.forEach(f -> f.cancel(true));
     206                    lastFutures.clear();
     207                    // Timeout after a minute to avoid blocking the worker thread.
     208                    results = pool.invokeAll(tasks, 1, TimeUnit.MINUTES);
     209                    lastFutures.addAll(results);
     210                }
     211
     212                if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     213                    waitForCompletedTasks(results, startTimeDownloadCubemapImages);
     214                }
     215            } catch (InterruptedException e) {
     216                Thread.currentThread().interrupt();
     217                LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     218            }
    166219        });
    167       } else {
    168 
    169         // launch 4-tiled (low-res) downloading tasks . . .
    170         if (Boolean.FALSE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())) {
    171           // download all imagery for each cubeface at once
    172 
    173           for (int i = 0; i < CubemapUtils.NUM_SIDES; i++) {
    174             int tileNr = 0;
    175             for (int j = 0; j < CubemapUtils.getMaxCols(); j++) {
    176               for (int k = 0; k < CubemapUtils.getMaxRows(); k++) {
    177 
    178                 String tileId = imageId + CubemapUtils.getFaceNumberForCount(i) + tileNr++;
    179                 tasks.add(new TileDownloadingTask(tileId));
    180               }
    181             }
    182           }
    183           // launch 16-tiled (high-res) downloading tasks
    184         } else if (Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())) {
    185 
    186           for (int i = 0; i < CubemapUtils.NUM_SIDES; i++) {
    187             for (int j = 0; j < CubemapUtils.getMaxCols(); j++) {
    188               for (int k = 0; k < CubemapUtils.getMaxRows(); k++) {
    189 
    190                 String tileId = imageId + CubemapUtils.getFaceNumberForCount(i) + j + k;
    191                 tasks.add(new TileDownloadingTask(tileId));
    192               }
    193             }
    194           }
    195         }
    196       } // finish preparing tasks for invocation
    197 
    198       // execute tasks
    199       MainApplication.worker.submit(() -> {
    200         try {
    201           List<Future<List<String>>> results = pool.invokeAll(tasks);
    202 
    203           if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get()) && results != null) {
    204             for (Future<List<String>> ff : results) {
    205               try {
     220
     221        long stopTime = System.currentTimeMillis();
     222        long runTime = stopTime - startTimeDownloadCubemapImages;
     223
     224        if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     225            LOGGER.log(Logging.LEVEL_DEBUG, "Tile imagery downloading tasks completed in {0} seconds.", runTime / 1000);
     226        }
     227
     228        return Collections.emptyMap(); // FIXME: Actually return something for cancelling
     229    }
     230
     231    /**
     232     * Wait for completed tasks and log errors
     233     * @param results The list of tasks to wait for
     234     * @param startTimeDownloadCubemapImages The time that downloads started
     235     * @throws InterruptedException If this thread was interrupted (see {@link Future#get()})
     236     */
     237    private static void waitForCompletedTasks(List<Future<List<String>>> results, long startTimeDownloadCubemapImages)
     238            throws InterruptedException {
     239        for (Future<List<String>> ff : results) {
     240            try {
     241                LOGGER.log(Logging.LEVEL_DEBUG, "Completed tile downloading task {0} in {1} seconds.", new Object[] {
     242                        ff.get(), (System.currentTimeMillis() - startTimeDownloadCubemapImages) / 1000 });
     243            } catch (ExecutionException e) {
     244                LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     245            }
     246        }
     247    }
     248
     249    /**
     250     * Fired when a TileDownloadingTask has completed downloading an image tile. When all the tiles for the Cubemap
     251     * have been downloaded, the CubemapBuilder assembles the cubemap.
     252     *
     253     * @param image The image that the task has finished downloading for
     254     * @param tileXY
     255     *      the complete quadKey of the imagery tile, including cubeface and row/column in quaternary.
     256     * @param bufferedImage The image for the tile
     257     * @see TileDownloadingTask
     258     */
     259    @Override
     260    public void tileAdded(StreetsideAbstractImage image, CubeMapTileXY tileXY, BufferedImage bufferedImage) {
     261        // determine whether four tiles have been set for each of the
     262        // six cubemap faces. If so, build the images for the faces
     263        // and set the views in the cubemap box.
     264
     265        if (!cubemap.id().equals(image.id())) {
     266            return;
     267        }
     268        this.addTileImage(tileXY, bufferedImage);
     269
     270        if (currentTileCount.incrementAndGet() == (CubemapUtils.NUM_SIDES * CubemapUtils.getMaxCols(image)
     271                * CubemapUtils.getMaxRows(image))) {
     272            if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     273                long endTime = System.currentTimeMillis();
     274                long runTime = (endTime - startTime) / 1000;
    206275                LOGGER.log(Logging.LEVEL_DEBUG,
    207                     MessageFormat.format("Completed tile downloading task {0} in {1} seconds.",
    208                         ff.get().toString(), (System.currentTimeMillis() - startTime) / 1000));
    209               } catch (ExecutionException e) {
    210                 LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
    211               }
    212             }
    213           }
    214         } catch (InterruptedException e) {
    215           LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
    216         }
    217       });
    218     } catch (Exception ee) {
    219       fails++;
    220       LOGGER.log(Logging.LEVEL_ERROR, ee, () -> "Error loading tile for image " + imageId);
    221     }
    222 
    223     long stopTime = System.currentTimeMillis();
    224     long runTime = stopTime - startTime;
    225 
    226     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    227       LOGGER.log(Logging.LEVEL_DEBUG,
    228           MessageFormat.format("Tile imagery downloading tasks completed in {0} seconds.", runTime / 1000));
    229     }
    230 
    231     if (fails > 0) {
    232       LOGGER.log(Logging.LEVEL_ERROR, fails + " downloading tasks failed!");
    233     }
    234   }
    235 
    236   /**
    237    * Fired when a TileDownloadingTask has completed downloading an image tile. When all of the tiles for the Cubemap
    238    * have been downloaded, the CubemapBuilder assembles the cubemap.
    239    *
    240    * @param tileId
    241    *      the complete quadKey of the imagery tile, including cubeface and row/column in quaternary.
    242    * @see TileDownloadingTask
    243    */
    244   @Override
    245   public void tileAdded(String tileId) {
    246     // determine whether four tiles have been set for each of the
    247     // six cubemap faces. If so, build the images for the faces
    248     // and set the views in the cubemap box.
    249 
    250     if (!tileId.startsWith(cubemap.getId())) {
    251       return;
    252     }
    253 
    254     currentTileCount++;
    255 
    256     if (currentTileCount == (CubemapUtils.NUM_SIDES * CubemapUtils.getMaxCols() * CubemapUtils.getMaxRows())) {
    257       if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     276                        "{0} tile images ready for building cumbemap faces for cubemap {1} in {2} seconds.",
     277                        new Object[] { currentTileCount.get(), CubemapBuilder.getInstance().getCubemap().id(),
     278                                Long.toString(runTime) });
     279            }
     280
     281            buildCubemapFaces();
     282        }
     283    }
     284
     285    /**
     286     * Assembles the cubemap once all the tiles have been downloaded.
     287     * <p>
     288     * The tiles for each cubemap face are cropped and stitched together
     289     * then the ImageViews of the cubemap are set with the new imagery.
     290     *
     291     * @see StreetsideAbstractImage
     292     */
     293    private void buildCubemapFaces() {
     294        StreetsideViewerDialog.getInstance();
     295        final var cubemapBox = StreetsideViewerPanel.getCubemapBox();
     296        final var views = cubemapBox.getViews();
     297
     298        final var finalImages = new Image[CubemapUtils.NUM_SIDES];
     299
     300        // build 4-tiled cubemap faces and crop buffers
     301        final int zoom = Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())
     302                ? this.cubemap.zoomMax()
     303                : this.cubemap.zoomMin();
     304        for (var i = 0; i < CubemapUtils.NUM_SIDES; i++) {
     305            final var face = CubemapUtils.CubemapFaces.values()[i];
     306            final Map<CubeMapTileXY, BufferedImage> tiles;
     307            synchronized (this.tileImages) {
     308                tiles = this.cubemap.getFaceTiles(face, zoom).filter(p -> tileImages.get(p.a) != null)
     309                        .collect(Collectors.toMap(p -> p.a, p -> tileImages.get(p.a)));
     310            }
     311            if (this.cubemap.getFaceTiles(face, zoom).count() != tiles.size()) {
     312                return; // We don't have all the sides yet. FIXME
     313            }
     314
     315            final var tImage = GraphicsUtils.buildMultiTiledCubemapFaceImage(tiles, zoom);
     316            // We need to flip the image horizontally (at least with JavaFX).
     317            final var finalImg = new BufferedImage(tImage.getWidth(), tImage.getHeight(), tImage.getType());
     318            final var g2d = finalImg.createGraphics();
     319            final var translate = AffineTransform.getScaleInstance(-1, 1);
     320            translate.concatenate(AffineTransform.getTranslateInstance(-finalImg.getWidth(), 0));
     321            // rotate top/down cubeface 180 degrees - misalignment workaround (this could probably be worked around in
     322            // CubemapBox).
     323            g2d.drawImage(face == CubemapUtils.CubemapFaces.DOWN || face == CubemapUtils.CubemapFaces.UP
     324                    ? GraphicsUtils.rotateImage(tImage)
     325                    : tImage, new AffineTransformOp(translate, AffineTransformOp.TYPE_BILINEAR), 0, 0);
     326            g2d.dispose();
     327            finalImages[i] = SwingFXUtils.toFXImage(finalImg, null);
     328        }
     329
     330        for (var i = 0; i < CubemapUtils.NUM_SIDES; i++) {
     331            views[i].setImage(finalImages[i]);
     332        }
     333
     334        StreetsideViewerDialog.getInstance().getStreetsideViewerPanel().revalidate();
     335        StreetsideViewerDialog.getInstance().getStreetsideViewerPanel().repaint();
     336
     337        StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel()
     338                .setScene(StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel().getCubemapScene());
     339
     340        StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel().revalidate();
     341        StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel().repaint();
     342
    258343        long endTime = System.currentTimeMillis();
    259344        long runTime = (endTime - startTime) / 1000;
    260         LOGGER.log(Logging.LEVEL_DEBUG, MessageFormat.format(
    261             "{0} tile images ready for building cumbemap faces for cubemap {1} in {2} seconds.",
    262             currentTileCount, CubemapBuilder.getInstance().getCubemap().getId(), Long.toString(runTime)));
    263       }
    264 
    265       buildCubemapFaces();
    266     }
    267   }
    268 
    269   /**
    270    * Assembles the cubemap once all of the tiles have been downloaded.
    271    * <p>
    272    * The tiles for each cubemap face are cropped and stitched together
    273    * then the ImageViews of the cubemap are set with the new imagery.
    274    *
    275    * @see     StreetsideCubemap
    276    */
    277   private void buildCubemapFaces() {
    278     StreetsideViewerDialog.getInstance();
    279     CubemapBox cmb = StreetsideViewerPanel.getCubemapBox();
    280     ImageView[] views = cmb.getViews();
    281 
    282     Image[] finalImages = new Image[CubemapUtils.NUM_SIDES];
    283 
    284     // build 4-tiled cubemap faces and crop buffers
    285     if (Boolean.FALSE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())) {
    286       for (int i = 0; i < CubemapUtils.NUM_SIDES; i++) {
    287 
    288         BufferedImage[] faceTileImages = new BufferedImage[CubemapUtils.getMaxCols()
    289             * CubemapUtils.getMaxRows()];
    290 
    291         for (int j = 0; j < (CubemapUtils.getMaxCols() * CubemapUtils.getMaxRows()); j++) {
    292           String tileId = getCubemap().getId() + CubemapUtils.getFaceNumberForCount(i) + j;
    293           BufferedImage currentTile = tileImages.get(tileId);
    294 
    295           faceTileImages[j] = currentTile;
    296         }
    297 
    298         BufferedImage finalImg = GraphicsUtils.buildMultiTiledCubemapFaceImage(faceTileImages);
    299 
    300         // rotate top cubeface 180 degrees - misalignment workaround
    301         if (i == 4) {
    302           finalImg = GraphicsUtils.rotateImage(finalImg);
    303         }
    304         finalImages[i] = GraphicsUtils.convertBufferedImage2JavaFXImage(finalImg);
    305       }
    306       // build 16-tiled cubemap faces and crop buffers
    307     } else if (Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())) {
    308       for (int i = 0; i < CubemapUtils.NUM_SIDES; i++) {
    309 
    310         int tileCount = 0;
    311 
    312         BufferedImage[] faceTileImages = new BufferedImage[Boolean.TRUE
    313             .equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) ? 16 : 4];
    314 
    315         for (int j = 0; j < CubemapUtils.getMaxCols(); j++) {
    316           for (int k = 0; k < CubemapUtils.getMaxRows(); k++) {
    317             String tileId = getCubemap().getId() + CubemapUtils.getFaceNumberForCount(i)
    318                 + CubemapUtils.convertDoubleCountNrto16TileNr(j + Integer.toString(k));
    319             BufferedImage currentTile = tileImages.get(tileId);
    320             faceTileImages[tileCount++] = currentTile;
    321           }
    322         }
    323         BufferedImage finalImg = GraphicsUtils.buildMultiTiledCubemapFaceImage(faceTileImages);
    324         // rotate top cubeface 180 degrees - misalignment workaround
    325         if (i == 4) {
    326           finalImg = GraphicsUtils.rotateImage(finalImg);
    327         }
    328         finalImages[i] = GraphicsUtils.convertBufferedImage2JavaFXImage(finalImg);
    329       }
    330     }
    331 
    332     for (int i = 0; i < CubemapUtils.NUM_SIDES; i++) {
    333       views[i].setImage(finalImages[i]);
    334     }
    335 
    336     StreetsideViewerDialog.getInstance().getStreetsideViewerPanel().revalidate();
    337     StreetsideViewerDialog.getInstance().getStreetsideViewerPanel().repaint();
    338 
    339     StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel()
    340         .setScene(StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel().getCubemapScene());
    341 
    342     StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel().revalidate();
    343     StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel().repaint();
    344 
    345     long endTime = System.currentTimeMillis();
    346     long runTime = (endTime - startTime) / 1000;
    347 
    348     String message = MessageFormat.format(
    349         "Completed downloading, assembling and setting cubemap imagery for cubemap {0} in  {1} seconds.",
    350         cubemap.getId(), runTime);
    351 
    352     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    353       LOGGER.log(Logging.LEVEL_DEBUG, message);
    354     }
    355 
    356     // reset count and image map after assembly
    357     resetTileImages();
    358     currentTileCount = 0;
    359     isBuilding = false;
    360   }
    361 
    362   private void resetTileImages() {
    363     tileImages = new HashMap<>();
    364   }
    365 
    366   /**
    367    * @return the cubemap
    368    */
    369   public synchronized StreetsideCubemap getCubemap() {
    370     return cubemap;
    371   }
    372 
    373   /**
    374    * @param cubemap
    375    *      the cubemap to set
    376    */
    377   public static void setCubemap(StreetsideCubemap cubemap) {
    378     CubemapBuilder.getInstance().cubemap = cubemap;
    379   }
    380 
    381   /**
    382    * @return the isBuilding
    383    */
    384   public boolean isBuilding() {
    385     return isBuilding;
    386   }
     345
     346        String message = MessageFormat.format(
     347                "Completed downloading, assembling and setting cubemap imagery for cubemap {0} in  {1} seconds.",
     348                cubemap.id(), runTime);
     349
     350        if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     351            LOGGER.log(Logging.LEVEL_DEBUG, message);
     352        }
     353
     354        // reset count and image map after assembly
     355        resetTileImages();
     356        currentTileCount.set(0);
     357        isBuilding = false;
     358    }
     359
     360    private void resetTileImages() {
     361        tileImages.clear();
     362    }
     363
     364    /**
     365     * Get the current image that is providing the cubemap data
     366     * @return the cubemap
     367     */
     368    public synchronized StreetsideAbstractImage getCubemap() {
     369        return cubemap;
     370    }
    387371}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cubemap/CubemapUtils.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside.cubemap;
    33
    4 import java.text.MessageFormat;
    5 import java.util.HashMap;
    6 import java.util.Map;
    74import java.util.logging.Logger;
    8 import java.util.stream.Stream;
    95
     6import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    107import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
    118import org.openstreetmap.josm.tools.Logging;
    129
    13 public class CubemapUtils {
     10/**
     11 * Utils for cubemaps
     12 */
     13public final class CubemapUtils {
    1414
    15   public static final String TEST_IMAGE_ID = "00000000";
    16   public static final int NUM_SIDES = 6;
    17   private static final Logger LOGGER = Logger.getLogger(CubemapUtils.class.getCanonicalName());
    18   // numerical base for decimal conversion (quaternary in the case of Streetside)
    19   private static final int NUM_BASE = 4;
    20   public static Map<String[], String> directionConversion = new HashMap<>();
    21   public static Map<String, String> rowCol2StreetsideCellAddressMap = null;
     15    public static final int NUM_SIDES = 6;
     16    private static final Logger LOGGER = Logger.getLogger(CubemapUtils.class.getCanonicalName());
     17    // numerical base for decimal conversion (quaternary in the case of Streetside)
     18    private static final int NUM_BASE = 4;
    2219
    23   // Intialize utility map for storing row to Streetside cell number conversions
    24   static {
    25 
    26     CubemapUtils.rowCol2StreetsideCellAddressMap = new HashMap<>();
    27     CubemapUtils.rowCol2StreetsideCellAddressMap.put("00", "00");
    28     CubemapUtils.rowCol2StreetsideCellAddressMap.put("01", "01");
    29     CubemapUtils.rowCol2StreetsideCellAddressMap.put("02", "10");
    30     CubemapUtils.rowCol2StreetsideCellAddressMap.put("03", "11");
    31     CubemapUtils.rowCol2StreetsideCellAddressMap.put("10", "02");
    32     CubemapUtils.rowCol2StreetsideCellAddressMap.put("11", "03");
    33     CubemapUtils.rowCol2StreetsideCellAddressMap.put("12", "12");
    34     CubemapUtils.rowCol2StreetsideCellAddressMap.put("13", "13");
    35     CubemapUtils.rowCol2StreetsideCellAddressMap.put("20", "20");
    36     CubemapUtils.rowCol2StreetsideCellAddressMap.put("21", "21");
    37     CubemapUtils.rowCol2StreetsideCellAddressMap.put("22", "30");
    38     CubemapUtils.rowCol2StreetsideCellAddressMap.put("23", "31");
    39     CubemapUtils.rowCol2StreetsideCellAddressMap.put("30", "22");
    40     CubemapUtils.rowCol2StreetsideCellAddressMap.put("31", "23");
    41     CubemapUtils.rowCol2StreetsideCellAddressMap.put("32", "32");
    42     CubemapUtils.rowCol2StreetsideCellAddressMap.put("33", "33");
    43   }
    44 
    45   private CubemapUtils() {
    46     // Private constructor to avoid instantiation
    47   }
    48 
    49   public static int getMaxCols() {
    50     return Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) ? 4 : 2;
    51   }
    52 
    53   public static int getMaxRows() {
    54     return getMaxCols();
    55   }
    56 
    57   public static String convertDecimal2Quaternary(long inputNum) {
    58     String res = null;
    59     final StringBuilder sb = new StringBuilder();
    60 
    61     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    62       LOGGER.log(Logging.LEVEL_DEBUG,
    63           MessageFormat.format("convertDecimal2Quaternary input: {0}", Long.toString(inputNum)));
     20    private CubemapUtils() {
     21        // Private constructor to avoid instantiation
    6422    }
    6523
    66     while (inputNum > 0) {
    67       sb.append(inputNum % CubemapUtils.NUM_BASE);
    68       inputNum /= CubemapUtils.NUM_BASE;
     24    /**
     25     * Get the maximum columns for the image
     26     * @param image The image to get the max columns for
     27     * @return The maximum number of columns
     28     */
     29    public static int getMaxCols(StreetsideAbstractImage image) {
     30        if (Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())) {
     31            return image.xCols(image.zoomMax());
     32        }
     33        return image.xCols(image.zoomMin());
    6934    }
    7035
    71     res = sb.reverse().toString();
    72 
    73     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    74       LOGGER.log(Logging.LEVEL_DEBUG, MessageFormat.format("convertDecimal2Quaternary output: {0}", res));
     36    /**
     37     * Get the maximum rows for the image
     38     * @param image The image to get the max rows for
     39     * @return The maximum number of rows
     40     */
     41    public static int getMaxRows(StreetsideAbstractImage image) {
     42        return getMaxCols(image);
    7543    }
    7644
    77     return res;
    78   }
    79 
    80   public static String convertQuaternary2Decimal(String inputNum) {
    81 
    82     final String res;
    83 
    84     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    85       LOGGER.log(Logging.LEVEL_DEBUG, MessageFormat.format("convertQuaternary2Decimal input: {0}", inputNum));
     45    /**
     46     * Convert a decimal number to a quaternary (base 4) number
     47     * @param inputNum The number to convert
     48     * @return The quaternary as a string
     49     */
     50    public static String convertDecimal2Quaternary(long inputNum) {
     51        return Long.toString(inputNum, CubemapUtils.NUM_BASE);
    8652    }
    8753
    88     int len = inputNum.length();
    89     int power = 1;
    90     int num = 0;
    91     int i;
    92 
    93     for (i = len - 1; i >= 0; i--) {
    94       if (Integer.parseInt(inputNum.substring(i, i + 1)) >= CubemapUtils.NUM_BASE) {
    95         LOGGER.log(Logging.LEVEL_ERROR, "Error converting quadkey " + inputNum + " to decimal.");
    96         return "000000000";
    97       }
    98 
    99       num += Integer.parseInt(inputNum.substring(i, i + 1)) * power;
    100       power = power * CubemapUtils.NUM_BASE;
     54    /**
     55     * Convert a quaternary number to a standard decimal number
     56     * @param inputNum The quaternary input number to convert
     57     * @return The standard decimal number
     58     */
     59    public static String convertQuaternary2Decimal(String inputNum) {
     60        try {
     61            return Long.toString(Long.valueOf(inputNum, CubemapUtils.NUM_BASE));
     62        } catch (NumberFormatException numberFormatException) {
     63            Logging.trace(numberFormatException);
     64            LOGGER.log(Logging.LEVEL_ERROR, "Error converting quadkey {0} to decimal.", inputNum);
     65            return "000000000";
     66        }
    10167    }
    10268
    103     res = Integer.toString(num);
     69    /**
     70     * The faces for a cubemap
     71     */
     72    public enum CubemapFaces {
     73        FRONT("01"), RIGHT("02"), BACK("03"), LEFT("10"), UP("11"), DOWN("12");
    10474
    105     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    106       LOGGER.log(Logging.LEVEL_DEBUG, MessageFormat.format("convertQuaternary2Decimal output: {0}", res));
     75        private final String value;
     76
     77        CubemapFaces(String value) {
     78            this.value = value;
     79        }
     80
     81        /**
     82         * The base value for the side
     83         * @return The base value for the side (top is 1-1 in Streetside API docs, here it is 11)
     84         */
     85        public String getValue() {
     86            return value;
     87        }
     88
     89        /**
     90         * The face id
     91         * @return The face id
     92         */
     93        public String faceId() {
     94            return this.value.substring(0, 1);
     95        }
     96
     97        /**
     98         * The starting tile id
     99         * @return The starting tile id
     100         */
     101        public String startingTileId() {
     102            return this.value.substring(1, 2);
     103        }
    107104    }
    108 
    109     return res;
    110   }
    111 
    112   public static String getFaceNumberForCount(int count) {
    113     final String res;
    114 
    115     switch (count) {
    116     case 0:
    117       res = CubemapFaces.FRONT.getValue();
    118       break;
    119     case 1:
    120       res = CubemapFaces.RIGHT.getValue();
    121       break;
    122     case 2:
    123       res = CubemapFaces.BACK.getValue();
    124       break;
    125     case 3:
    126       res = CubemapFaces.LEFT.getValue();
    127       break;
    128     case 4:
    129       res = CubemapFaces.UP.getValue();
    130       break;
    131     case 5:
    132       res = CubemapFaces.DOWN.getValue();
    133       break;
    134     default:
    135       res = null;
    136       break;
    137     }
    138     return res;
    139   }
    140 
    141   public static int getTileWidth() {
    142     // 4-tiled cubemap imagery has a 2-pixel overlap; 16-tiled has a 1-pixel
    143     // overlap
    144     if (Boolean.FALSE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())) {
    145       return 255;
    146     } else {
    147       return 254;
    148     }
    149   }
    150 
    151   public static int getTileHeight() {
    152     // 4-tiled cubemap imagery has a 2-pixel overlap; 16-tiled has a 1-pixel
    153     // overlap
    154     if (Boolean.FALSE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())) {
    155       return 255;
    156     } else {
    157       return 254;
    158     }
    159   }
    160 
    161   public static int getCount4FaceNumber(String faceString) {
    162 
    163     final int tileAddress;
    164 
    165     switch (faceString) {
    166     // back
    167     case "03":
    168       tileAddress = 0;
    169       break;
    170     // down
    171     case "12":
    172       tileAddress = 1;
    173       break;
    174     // front
    175     case "01":
    176       tileAddress = 2;
    177       break;
    178     // left
    179     case "10":
    180       tileAddress = 3;
    181       break;
    182     // right
    183     case "02":
    184       tileAddress = 4;
    185       break;
    186     // up
    187     case "11":
    188       tileAddress = 5;
    189       break;
    190     default:
    191       tileAddress = 6;
    192       break;
    193     }
    194 
    195     return tileAddress;
    196   }
    197 
    198   public static String getFaceIdFromTileId(String tileId) {
    199     // magic numbers - the face id is contained in the 16th and 17th positions
    200     return tileId.substring(16, 18);
    201   }
    202 
    203   public static String convertDoubleCountNrto16TileNr(String countNr) {
    204     String tileAddress;
    205 
    206     switch (countNr) {
    207     case "00":
    208       tileAddress = "00";
    209       break;
    210     case "01":
    211       tileAddress = "01";
    212       break;
    213     case "02":
    214       tileAddress = "10";
    215       break;
    216     case "03":
    217       tileAddress = "11";
    218       break;
    219     case "10":
    220       tileAddress = "02";
    221       break;
    222     case "11":
    223       tileAddress = "03";
    224       break;
    225     case "12":
    226       tileAddress = "12";
    227       break;
    228     case "13":
    229       tileAddress = "13";
    230       break;
    231     case "20":
    232       tileAddress = "20";
    233       break;
    234     case "21":
    235       tileAddress = "21";
    236       break;
    237     case "22":
    238       tileAddress = "30";
    239       break;
    240     case "23":
    241       tileAddress = "31";
    242       break;
    243     case "30":
    244       tileAddress = "22";
    245       break;
    246     case "31":
    247       tileAddress = "23";
    248       break;
    249     case "32":
    250       tileAddress = "32";
    251       break;
    252     case "33":
    253       tileAddress = "33";
    254       break;
    255     // shouldn't happen
    256     default:
    257       tileAddress = null;
    258       break;
    259     }
    260 
    261     return tileAddress;
    262   }
    263 
    264   public enum CubefaceType {
    265     ONE(1), FOUR(4), SIXTEEN(16);
    266 
    267     private static final Map<Integer, CubefaceType> map = new HashMap<>();
    268 
    269     static {
    270       for (CubefaceType cubefaceType : CubefaceType.values()) {
    271         map.put(cubefaceType.value, cubefaceType);
    272       }
    273     }
    274 
    275     private final int value;
    276 
    277     CubefaceType(int value) {
    278       this.value = value;
    279     }
    280 
    281     public static CubefaceType valueOf(int cubefaceType) {
    282       return map.get(cubefaceType);
    283     }
    284 
    285     public int getValue() {
    286       return value;
    287     }
    288   }
    289 
    290   public enum CubemapFaces {
    291     FRONT("01"), RIGHT("02"), BACK("03"), LEFT("10"), UP("11"), DOWN("12");
    292 
    293     private final String value;
    294 
    295     CubemapFaces(String value) {
    296       this.value = value;
    297     }
    298 
    299     public static Stream<CubemapFaces> stream() {
    300       return Stream.of(CubemapFaces.values());
    301     }
    302 
    303     public String getValue() {
    304       return value;
    305     }
    306   }
    307105}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cubemap/ITileDownloadingTaskListener.java

    r36194 r36228  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.plugins.streetside.cubemap;
     3
     4import java.awt.image.BufferedImage;
     5
     6import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY;
     7import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    38
    49/**
     
    1015public interface ITileDownloadingTaskListener {
    1116
    12   /**
    13    * Fired when a cubemap tile image is downloaded by a download worker.
    14    *
    15    * @param imageId image id
    16    */
    17   void tileAdded(String imageId);
     17    /**
     18     * Fired when a cubemap tile image is downloaded by a download worker.
     19     *
     20     * @param image The image for which we are downloading tiles
     21     * @param tile The tile that we downloaded an image for
     22     * @param tileImage The image for the tile
     23     */
     24    void tileAdded(StreetsideAbstractImage image, CubeMapTileXY tile, BufferedImage tileImage);
    1825
    1926}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cubemap/TileDownloadingTask.java

    r36194 r36228  
    44import java.awt.image.BufferedImage;
    55import java.io.IOException;
     6import java.net.URI;
    67import java.text.MessageFormat;
    78import java.util.ArrayList;
     9import java.util.Collections;
    810import java.util.List;
    911import java.util.Objects;
    1012import java.util.concurrent.Callable;
    1113import java.util.concurrent.CopyOnWriteArrayList;
     14import java.util.concurrent.Semaphore;
    1215import java.util.logging.Logger;
    1316
    1417import javax.imageio.ImageIO;
    1518
    16 import org.openstreetmap.josm.plugins.streetside.cache.StreetsideCache;
     19import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY;
     20import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    1721import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
    18 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL;
     22import org.openstreetmap.josm.spi.preferences.Config;
    1923import org.openstreetmap.josm.tools.Logging;
    2024
     25/**
     26 * A task for downloading tiles of an image
     27 */
    2128public class TileDownloadingTask implements Callable<List<String>> {
    2229
    23   private static final Logger LOGGER = Logger.getLogger(TileDownloadingTask.class.getCanonicalName());
    24   /**
    25    * Listeners of the class.
    26    */
    27   private final List<ITileDownloadingTaskListener> listeners = new CopyOnWriteArrayList<>();
    28   protected CubemapBuilder cb;
    29   boolean cancelled;
    30   private String tileId;
    31   private StreetsideCache cache;
     30    private static final Logger LOGGER = Logger.getLogger(TileDownloadingTask.class.getCanonicalName());
     31    private static final Semaphore limitConnections = new Semaphore(
     32            CubemapUtils.NUM_SIDES * Config.getPref().getInt("streetside.download.threads.per.side", 64)); // z3 is 8x8
     33    /**
     34     * Listeners of the class.
     35     */
     36    private final List<ITileDownloadingTaskListener> listeners = new CopyOnWriteArrayList<>();
     37    private final StreetsideAbstractImage image;
     38    private final CubemapUtils.CubemapFaces face;
     39    private final CubeMapTileXY tileId;
     40    protected final CubemapBuilder cb;
    3241
    33   public TileDownloadingTask(String id) {
    34     tileId = id;
    35     cb = CubemapBuilder.getInstance();
    36     addListener(CubemapBuilder.getInstance());
    37   }
     42    /**
     43     * Download a tile
     44     * @param image The image that we are downloading the tile for
     45     * @param face The face for the image (since Streetside provides cubemaps)
     46     * @param tileId The tile id to download
     47     */
     48    public TileDownloadingTask(StreetsideAbstractImage image, CubemapUtils.CubemapFaces face, CubeMapTileXY tileId) {
     49        this.image = image;
     50        this.face = face;
     51        this.tileId = tileId;
     52        this.cb = CubemapBuilder.getInstance();
     53        addListener(this.cb);
     54    }
    3855
    39   /**
    40    * Adds a new listener.
    41    *
    42    * @param lis Listener to be added.
    43    */
    44   public final void addListener(final ITileDownloadingTaskListener lis) {
    45     listeners.add(lis);
    46   }
     56    /**
     57     * Adds a new listener.
     58     *
     59     * @param lis Listener to be added.
     60     */
     61    public final void addListener(final ITileDownloadingTaskListener lis) {
     62        listeners.add(lis);
     63    }
    4764
    48   /**
    49    * @return the tileId
    50    */
    51   public String getId() {
    52     return tileId;
    53   }
     65    /**
     66     * Get the id for this task
     67     * @return the tileId
     68     */
     69    public String getId() {
     70        return tileId.toString();
     71    }
    5472
    55   /**
    56    * @param id the tileId to set
    57    */
    58   public void setId(String id) {
    59     tileId = id;
    60   }
     73    @Override
     74    public List<String> call() {
     75        try {
     76            limitConnections.acquire();
     77            try {
     78                return download();
     79            } finally {
     80                limitConnections.release();
     81            }
     82        } catch (InterruptedException e) {
     83            Thread.currentThread().interrupt();
     84            Logging.trace(e);
     85            return Collections.emptyList();
     86        }
     87    }
    6188
    62   /**
    63    * @return the cache
    64    */
    65   public StreetsideCache getCache() {
    66     return cache;
    67   }
     89    private List<String> download() {
     90        List<String> res = new ArrayList<>();
    6891
    69   /**
    70    * @param cache the cache to set
    71    */
    72   public void setCache(StreetsideCache cache) {
    73     this.cache = cache;
    74   }
     92        final int zoom = Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())
     93                ? this.image.zoomMax()
     94                : this.image.zoomMin();
     95        if (Boolean.TRUE.equals(StreetsideProperties.DOWNLOAD_CUBEFACE_TILES_TOGETHER.get())) {
     96            // download all imagery for each cubeface at once
     97            res.addAll(this.image.getFaceTiles(this.face, zoom).map(pair -> downloadTile(pair.a, pair.b)).toList());
     98            // task downloads just one tile
     99        } else {
     100            res.add(downloadTile(tileId, this.image.getTile(this.face.getValue(), tileId.getQuadKey(zoom))));
     101        }
     102        return res;
     103    }
    75104
    76   /**
    77    * @return the cb
    78    */
    79   public CubemapBuilder getCb() {
    80     return cb;
    81   }
     105    private String downloadTile(CubeMapTileXY tile, String url) {
     106        BufferedImage img;
    82107
    83   /**
    84    * @param cb the cb to set
    85    */
    86   public void setCb(CubemapBuilder cb) {
    87     this.cb = cb;
    88   }
     108        long startTime = System.currentTimeMillis();
    89109
    90   /**
    91    * @param cancelled the cancelled to set
    92    */
    93   public void setCancelled(boolean cancelled) {
    94     this.cancelled = cancelled;
    95   }
     110        try {
     111            img = ImageIO.read(URI.create(url).toURL());
    96112
    97   @Override
    98   public List<String> call() throws Exception {
     113            if (img == null) {
     114                LOGGER.log(Logging.LEVEL_ERROR, "Download of BufferedImage {0} is null!", url);
     115            }
    99116
    100     List<String> res = new ArrayList<>();
     117            fireTileAdded(this.image, tile, img);
    101118
    102     if (Boolean.TRUE.equals(StreetsideProperties.DOWNLOAD_CUBEFACE_TILES_TOGETHER.get())) {
    103       // download all imagery for each cubeface at once
    104       if (Boolean.FALSE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())) {
    105         // download low-res imagery
    106         int tileNr = 0;
    107         for (int j = 0; j < CubemapUtils.getMaxCols(); j++) {
    108           for (int k = 0; k < CubemapUtils.getMaxRows(); k++) {
    109             String quadKey = tileId + tileNr++;
    110             res.add(downloadTile(quadKey));
    111           }
     119            if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     120                long endTime = System.currentTimeMillis();
     121                long runTime = (endTime - startTime) / 1000;
     122                LOGGER.log(Logging.LEVEL_DEBUG, "Loaded image for {0} in {1} seconds.", new Object[] { url, runTime });
     123            }
     124        } catch (IOException e) {
     125            LOGGER.log(Logging.LEVEL_ERROR, MessageFormat.format("Error downloading image for tileId {0}", url), e);
     126            return null;
    112127        }
    113         // download high-res imagery
    114       } else {
    115         for (int j = 0; j < CubemapUtils.getMaxCols(); j++) {
    116           for (int k = 0; k < CubemapUtils.getMaxRows(); k++) {
    117             String quadKey = tileId + j + k;
    118             res.add(downloadTile(quadKey));
    119           }
    120         }
    121       }
    122       // task downloads just one tile
    123     } else {
    124       res.add(downloadTile(tileId));
     128        return url;
    125129    }
    126     return res;
    127   }
    128130
    129   private String downloadTile(String tileId) {
    130     BufferedImage img;
    131 
    132     long startTime = System.currentTimeMillis();
    133 
    134     try {
    135       img = ImageIO.read(StreetsideURL.VirtualEarth.streetsideTile(tileId, false));
    136 
    137       if (img == null) {
    138         LOGGER.log(Logging.LEVEL_ERROR, "Download of BufferedImage " + tileId + " is null!");
    139       }
    140 
    141       CubemapBuilder.getInstance().getTileImages().put(tileId, img);
    142 
    143       fireTileAdded(tileId);
    144 
    145       if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    146         long endTime = System.currentTimeMillis();
    147         long runTime = (endTime - startTime) / 1000;
    148         LOGGER.log(Logging.LEVEL_DEBUG,
    149             MessageFormat.format("Loaded image for {0} in {1} seconds.", tileId, runTime));
    150       }
    151     } catch (IOException e) {
    152       LOGGER.log(Logging.LEVEL_ERROR, MessageFormat.format("Error downloading image for tileId {0}", tileId), e);
    153       return null;
     131    /**
     132     * Fire a tile add event
     133     * @param image The Streetside image we are getting tiled images for
     134     * @param tile The tile in the image
     135     * @param tileImage The actual tile image
     136     */
     137    private void fireTileAdded(StreetsideAbstractImage image, CubeMapTileXY tile, BufferedImage tileImage) {
     138        listeners.stream().filter(Objects::nonNull).forEach(lis -> lis.tileAdded(image, tile, tileImage));
    154139    }
    155     return tileId;
    156   }
    157 
    158   private void fireTileAdded(String id) {
    159     listeners.stream().filter(Objects::nonNull).forEach(lis -> lis.tileAdded(id));
    160   }
    161140}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsideImageDisplay.java

    r36194 r36228  
    1212import java.awt.Point;
    1313import java.awt.Rectangle;
    14 import java.awt.Shape;
    1514import java.awt.event.MouseEvent;
    1615import java.awt.event.MouseListener;
     
    2120import java.awt.geom.Rectangle2D;
    2221import java.awt.image.BufferedImage;
    23 import java.util.ArrayList;
    24 import java.util.Collection;
     22import java.io.Serial;
    2523
    2624import javax.swing.JComponent;
     
    2826import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
    2927import org.openstreetmap.josm.plugins.streetside.actions.StreetsideDownloadAction;
    30 import org.openstreetmap.josm.plugins.streetside.model.ImageDetection;
    31 import org.openstreetmap.josm.plugins.streetside.model.MapObject;
    32 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideColorScheme;
    3328import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
    3429
     
    4338public class StreetsideImageDisplay extends JComponent {
    4439
    45   private static final long serialVersionUID = -3188274185432686201L;
    46 
    47   private final Collection<ImageDetection> detections = new ArrayList<>();
    48 
    49   /**
    50    * The image currently displayed
    51    */
    52   private volatile BufferedImage image;
    53 
    54   /**
    55    * The rectangle (in image coordinates) of the image that is visible. This
    56    * rectangle is calculated each time the zoom is modified
    57    */
    58   private volatile Rectangle visibleRect;
    59 
    60   /**
    61    * When a selection is done, the rectangle of the selection (in image
    62    * coordinates)
    63    */
    64   private Rectangle selectedRect;
    65 
    66   /**
    67    * Main constructor.
    68    */
    69   public StreetsideImageDisplay() {
    70     ImgDisplayMouseListener mouseListener = new ImgDisplayMouseListener();
    71     addMouseListener(mouseListener);
    72     addMouseWheelListener(mouseListener);
    73     addMouseMotionListener(mouseListener);
    74 
    75     StreetsideProperties.SHOW_DETECTED_SIGNS.addListener(valChanged -> repaint());
    76   }
    77 
    78   private static Point getCenterImgCoord(Rectangle visibleRect) {
    79     return new Point(visibleRect.x + visibleRect.width / 2, visibleRect.y + visibleRect.height / 2);
    80   }
    81 
    82   /**
    83    * calculateDrawImageRectangle
    84    *
    85    * @param imgRect  the part of the image that should be drawn (in image coordinates)
    86    * @param compRect the part of the component where the image should be drawn (in
    87    *         component coordinates)
    88    * @return the part of compRect with the same width/height ratio as the image
    89    */
    90   private static Rectangle calculateDrawImageRectangle(Rectangle imgRect, Rectangle compRect) {
    91     int x = 0;
    92     int y = 0;
    93     int w = compRect.width;
    94     int h = compRect.height;
    95     int wFact = w * imgRect.height;
    96     int hFact = h * imgRect.width;
    97     if (wFact != hFact) {
    98       if (wFact > hFact) {
    99         w = hFact / imgRect.height;
    100         x = (compRect.width - w) / 2;
    101       } else {
    102         h = wFact / imgRect.width;
    103         y = (compRect.height - h) / 2;
    104       }
    105     }
    106     return new Rectangle(x + compRect.x, y + compRect.y, w, h);
    107   }
    108 
    109   private static void checkVisibleRectPos(Image image, Rectangle visibleRect) {
    110     if (visibleRect.x < 0) {
    111       visibleRect.x = 0;
    112     }
    113     if (visibleRect.y < 0) {
    114       visibleRect.y = 0;
    115     }
    116     if (visibleRect.x + visibleRect.width > image.getWidth(null)) {
    117       visibleRect.x = image.getWidth(null) - visibleRect.width;
    118     }
    119     if (visibleRect.y + visibleRect.height > image.getHeight(null)) {
    120       visibleRect.y = image.getHeight(null) - visibleRect.height;
    121     }
    122   }
    123 
    124   private static void checkVisibleRectSize(Image image, Rectangle visibleRect) {
    125     if (visibleRect.width > image.getWidth(null)) {
    126       visibleRect.width = image.getWidth(null);
    127     }
    128     if (visibleRect.height > image.getHeight(null)) {
    129       visibleRect.height = image.getHeight(null);
    130     }
    131   }
    132 
    133   /**
    134    * Sets a new picture to be displayed.
    135    *
    136    * @param image    The picture to be displayed.
    137    * @param detections image detections
    138    */
    139   public void setImage(BufferedImage image, Collection<ImageDetection> detections) {
    140     synchronized (this) {
    141       this.image = image;
    142       this.detections.clear();
    143       if (detections != null) {
    144         this.detections.addAll(detections);
    145       }
    146       selectedRect = null;
    147       if (image != null)
    148         visibleRect = new Rectangle(0, 0, image.getWidth(null), image.getHeight(null));
    149     }
    150     repaint();
    151   }
    152 
    153   /**
    154    * Returns the picture that is being displayed
    155    *
    156    * @return The picture that is being displayed.
    157    */
    158   public BufferedImage getImage() {
    159     return image;
    160   }
    161 
    162   /**
    163    * Paints the visible part of the picture.
    164    */
    165   @Override
    166   public void paintComponent(Graphics g) {
    167     Image image;
    168     Rectangle visibleRect;
    169     synchronized (this) {
    170       image = this.image;
    171       visibleRect = this.visibleRect;
    172     }
    173     if (image == null) {
    174       g.setColor(Color.black);
    175       String noImageStr = StreetsideLayer.hasInstance() ? tr("No image selected")
    176           : tr("Press \"{0}\" to download images", StreetsideDownloadAction.SHORTCUT.getKeyText());
    177       Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g);
    178       Dimension size = getSize();
    179       g.drawString(noImageStr, (int) ((size.width - noImageSize.getWidth()) / 2),
    180           (int) ((size.height - noImageSize.getHeight()) / 2));
    181     } else {
    182       Rectangle target = calculateDrawImageRectangle(visibleRect);
    183       g.drawImage(image, target.x, target.y, target.x + target.width, target.y + target.height, visibleRect.x,
    184           visibleRect.y, visibleRect.x + visibleRect.width, visibleRect.y + visibleRect.height, null);
    185       if (selectedRect != null) {
    186         Point topLeft = img2compCoord(visibleRect, selectedRect.x, selectedRect.y);
    187         Point bottomRight = img2compCoord(visibleRect, selectedRect.x + selectedRect.width,
    188             selectedRect.y + selectedRect.height);
    189         g.setColor(new Color(128, 128, 128, 180));
    190         g.fillRect(target.x, target.y, target.width, topLeft.y - target.y);
    191         g.fillRect(target.x, target.y, topLeft.x - target.x, target.height);
    192         g.fillRect(bottomRight.x, target.y, target.x + target.width - bottomRight.x, target.height);
    193         g.fillRect(target.x, bottomRight.y, target.width, target.y + target.height - bottomRight.y);
    194         g.setColor(Color.black);
    195         g.drawRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y);
    196       }
    197 
    198       if (Boolean.TRUE.equals(StreetsideProperties.SHOW_DETECTED_SIGNS.get())) {
    199         Point upperLeft = img2compCoord(visibleRect, 0, 0);
    200         Point lowerRight = img2compCoord(visibleRect, getImage().getWidth(), getImage().getHeight());
    201 
    202         // Transformation, which can convert you a Shape relative to the unit square to a Shape relative to the Component
    203         AffineTransform unit2compTransform = AffineTransform.getTranslateInstance(upperLeft.getX(),
    204             upperLeft.getY());
    205         unit2compTransform.concatenate(AffineTransform.getScaleInstance(lowerRight.getX() - upperLeft.getX(),
    206             lowerRight.getY() - upperLeft.getY()));
    207 
    208         final Graphics2D g2d = (Graphics2D) g;
    209         g2d.setStroke(new BasicStroke(2));
    210         for (ImageDetection d : detections) {
    211           final Shape shape = d.getShape().createTransformedShape(unit2compTransform);
    212           g2d.setColor(d.isTrafficSign() ? StreetsideColorScheme.IMAGEDETECTION_TRAFFICSIGN
    213               : StreetsideColorScheme.IMAGEDETECTION_UNKNOWN);
    214           g2d.draw(shape);
    215           if (d.isTrafficSign()) {
    216             g2d.drawImage(MapObject.getIcon(d.getValue()).getImage(), shape.getBounds().x,
    217                 shape.getBounds().y, shape.getBounds().width, shape.getBounds().height, null);
    218           }
    219         }
    220       }
    221     }
    222   }
    223 
    224   private Point img2compCoord(Rectangle visibleRect, int xImg, int yImg) {
    225     Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
    226     return new Point(drawRect.x + ((xImg - visibleRect.x) * drawRect.width) / visibleRect.width,
    227         drawRect.y + ((yImg - visibleRect.y) * drawRect.height) / visibleRect.height);
    228   }
    229 
    230   private Point comp2imgCoord(Rectangle visibleRect, int xComp, int yComp) {
    231     Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
    232     return new Point(visibleRect.x + ((xComp - drawRect.x) * visibleRect.width) / drawRect.width,
    233         visibleRect.y + ((yComp - drawRect.y) * visibleRect.height) / drawRect.height);
    234   }
    235 
    236   private Rectangle calculateDrawImageRectangle(Rectangle visibleRect) {
    237     return calculateDrawImageRectangle(visibleRect, new Rectangle(0, 0, getSize().width, getSize().height));
    238   }
    239 
    240   /**
    241    * Zooms to 1:1 and, if it is already in 1:1, to best fit.
    242    */
    243   public void zoomBestFitOrOne() {
    244     Image image;
    245     Rectangle visibleRect;
    246     synchronized (this) {
    247       image = this.image;
    248       visibleRect = this.visibleRect;
    249     }
    250     if (image == null)
    251       return;
    252     if (visibleRect.width != image.getWidth(null) || visibleRect.height != image.getHeight(null)) {
    253       // The display is not at best fit. => Zoom to best fit
    254       visibleRect = new Rectangle(0, 0, image.getWidth(null), image.getHeight(null));
    255     } else {
    256       // The display is at best fit => zoom to 1:1
    257       Point center = getCenterImgCoord(visibleRect);
    258       visibleRect = new Rectangle(center.x - getWidth() / 2, center.y - getHeight() / 2, getWidth(), getHeight());
    259       checkVisibleRectPos(image, visibleRect);
    260     }
    261     synchronized (this) {
    262       this.visibleRect = visibleRect;
    263     }
    264     repaint();
    265   }
    266 
    267   private class ImgDisplayMouseListener implements MouseListener, MouseWheelListener, MouseMotionListener {
    268     private boolean mouseIsDragging;
    269     private long lastTimeForMousePoint;
    270     private Point mousePointInImg;
    271 
    272     /**
    273      * Zoom in and out, trying to preserve the point of the image that was under
    274      * the mouse cursor at the same place
     40    @Serial
     41    private static final long serialVersionUID = -3188274185432686201L;
     42
     43    /**
     44     * The image currently displayed
     45     */
     46    private volatile BufferedImage image;
     47
     48    /**
     49     * The rectangle (in image coordinates) of the image that is visible. This
     50     * rectangle is calculated each time the zoom is modified
     51     */
     52    private volatile Rectangle visibleRect;
     53
     54    /**
     55     * When a selection is done, the rectangle of the selection (in image
     56     * coordinates)
     57     */
     58    private Rectangle selectedRect;
     59
     60    /**
     61     * Main constructor.
     62     */
     63    public StreetsideImageDisplay() {
     64        ImgDisplayMouseListener mouseListener = new ImgDisplayMouseListener();
     65        addMouseListener(mouseListener);
     66        addMouseWheelListener(mouseListener);
     67        addMouseMotionListener(mouseListener);
     68
     69        StreetsideProperties.SHOW_DETECTED_SIGNS.addListener(valChanged -> repaint());
     70    }
     71
     72    private static Point getCenterImgCoord(Rectangle visibleRect) {
     73        return new Point(visibleRect.x + visibleRect.width / 2, visibleRect.y + visibleRect.height / 2);
     74    }
     75
     76    /**
     77     * calculateDrawImageRectangle
     78     *
     79     * @param imgRect  the part of the image that should be drawn (in image coordinates)
     80     * @param compRect the part of the component where the image should be drawn (in
     81     *         component coordinates)
     82     * @return the part of compRect with the same width/height ratio as the image
     83     */
     84    private static Rectangle calculateDrawImageRectangle(Rectangle imgRect, Rectangle compRect) {
     85        int x = 0;
     86        int y = 0;
     87        int w = compRect.width;
     88        int h = compRect.height;
     89        int wFact = w * imgRect.height;
     90        int hFact = h * imgRect.width;
     91        if (wFact != hFact) {
     92            if (wFact > hFact) {
     93                w = hFact / imgRect.height;
     94                x = (compRect.width - w) / 2;
     95            } else {
     96                h = wFact / imgRect.width;
     97                y = (compRect.height - h) / 2;
     98            }
     99        }
     100        return new Rectangle(x + compRect.x, y + compRect.y, w, h);
     101    }
     102
     103    private static void checkVisibleRectPos(Image image, Rectangle visibleRect) {
     104        if (visibleRect.x < 0) {
     105            visibleRect.x = 0;
     106        }
     107        if (visibleRect.y < 0) {
     108            visibleRect.y = 0;
     109        }
     110        if (visibleRect.x + visibleRect.width > image.getWidth(null)) {
     111            visibleRect.x = image.getWidth(null) - visibleRect.width;
     112        }
     113        if (visibleRect.y + visibleRect.height > image.getHeight(null)) {
     114            visibleRect.y = image.getHeight(null) - visibleRect.height;
     115        }
     116    }
     117
     118    private static void checkVisibleRectSize(Image image, Rectangle visibleRect) {
     119        if (visibleRect.width > image.getWidth(null)) {
     120            visibleRect.width = image.getWidth(null);
     121        }
     122        if (visibleRect.height > image.getHeight(null)) {
     123            visibleRect.height = image.getHeight(null);
     124        }
     125    }
     126
     127    /**
     128     * Sets a new picture to be displayed.
     129     *
     130     * @param image    The picture to be displayed.
     131     */
     132    public void setImage(BufferedImage image) {
     133        synchronized (this) {
     134            this.image = image;
     135            selectedRect = null;
     136            if (image != null)
     137                visibleRect = new Rectangle(0, 0, image.getWidth(null), image.getHeight(null));
     138        }
     139        repaint();
     140    }
     141
     142    /**
     143     * Returns the picture that is being displayed
     144     *
     145     * @return The picture that is being displayed.
     146     */
     147    public BufferedImage getImage() {
     148        return image;
     149    }
     150
     151    /**
     152     * Paints the visible part of the picture.
    275153     */
    276154    @Override
    277     public void mouseWheelMoved(MouseWheelEvent e) {
    278       Image image;
    279       Rectangle visibleRect;
    280       synchronized (StreetsideImageDisplay.this) {
    281         image = getImage();
    282         visibleRect = StreetsideImageDisplay.this.visibleRect;
    283       }
    284       mouseIsDragging = false;
    285       selectedRect = null;
    286       if (image != null && Math.min(getSize().getWidth(), getSize().getHeight()) > 0) {
    287         // Calculate the mouse cursor position in image coordinates, so that
    288         // we can center the zoom
    289         // on that mouse position.
    290         // To avoid issues when the user tries to zoom in on the image
    291         // borders, this point is not calculated
    292         // again if there was less than 1.5seconds since the last event.
    293         if (e.getWhen() - lastTimeForMousePoint > 1500 || mousePointInImg == null) {
    294           lastTimeForMousePoint = e.getWhen();
    295           mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
    296         }
    297         // Set the zoom to the visible rectangle in image coordinates
    298         if (e.getWheelRotation() > 0) {
    299           visibleRect.width = visibleRect.width * 3 / 2;
    300           visibleRect.height = visibleRect.height * 3 / 2;
     155    public void paintComponent(Graphics g) {
     156        Image image;
     157        Rectangle visibleRect;
     158        synchronized (this) {
     159            image = this.image;
     160            visibleRect = this.visibleRect;
     161        }
     162        if (image == null) {
     163            g.setColor(Color.black);
     164            String noImageStr = StreetsideLayer.hasInstance() ? tr("No image selected")
     165                    : tr("Press \"{0}\" to download images", StreetsideDownloadAction.SHORTCUT.getKeyText());
     166            Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g);
     167            Dimension size = getSize();
     168            g.drawString(noImageStr, (int) ((size.width - noImageSize.getWidth()) / 2),
     169                    (int) ((size.height - noImageSize.getHeight()) / 2));
    301170        } else {
    302           visibleRect.width = visibleRect.width * 2 / 3;
    303           visibleRect.height = visibleRect.height * 2 / 3;
    304         }
    305         // Check that the zoom doesn't exceed 2:1
    306         if (visibleRect.width < getSize().width / 2) {
    307           visibleRect.width = getSize().width / 2;
    308         }
    309         if (visibleRect.height < getSize().height / 2) {
    310           visibleRect.height = getSize().height / 2;
    311         }
    312         // Set the same ratio for the visible rectangle and the display area
    313         int hFact = visibleRect.height * getSize().width;
    314         int wFact = visibleRect.width * getSize().height;
    315         if (hFact > wFact) {
    316           visibleRect.width = hFact / getSize().height;
    317         } else {
    318           visibleRect.height = wFact / getSize().width;
    319         }
    320         // The size of the visible rectangle is limited by the image size.
    321         checkVisibleRectSize(image, visibleRect);
    322         // Set the position of the visible rectangle, so that the mouse
    323         // cursor doesn't move on the image.
     171            Rectangle target = calculateDrawImageRectangle(visibleRect);
     172            g.drawImage(image, target.x, target.y, target.x + target.width, target.y + target.height, visibleRect.x,
     173                    visibleRect.y, visibleRect.x + visibleRect.width, visibleRect.y + visibleRect.height, null);
     174            if (selectedRect != null) {
     175                Point topLeft = img2compCoord(visibleRect, selectedRect.x, selectedRect.y);
     176                Point bottomRight = img2compCoord(visibleRect, selectedRect.x + selectedRect.width,
     177                        selectedRect.y + selectedRect.height);
     178                g.setColor(new Color(128, 128, 128, 180));
     179                g.fillRect(target.x, target.y, target.width, topLeft.y - target.y);
     180                g.fillRect(target.x, target.y, topLeft.x - target.x, target.height);
     181                g.fillRect(bottomRight.x, target.y, target.x + target.width - bottomRight.x, target.height);
     182                g.fillRect(target.x, bottomRight.y, target.width, target.y + target.height - bottomRight.y);
     183                g.setColor(Color.black);
     184                g.drawRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y);
     185            }
     186
     187            if (Boolean.TRUE.equals(StreetsideProperties.SHOW_DETECTED_SIGNS.get())) {
     188                Point upperLeft = img2compCoord(visibleRect, 0, 0);
     189                Point lowerRight = img2compCoord(visibleRect, getImage().getWidth(), getImage().getHeight());
     190
     191                // Transformation, which can convert you a Shape relative to the unit square to a Shape relative to the Component
     192                AffineTransform unit2compTransform = AffineTransform.getTranslateInstance(upperLeft.getX(),
     193                        upperLeft.getY());
     194                unit2compTransform.concatenate(AffineTransform.getScaleInstance(lowerRight.getX() - upperLeft.getX(),
     195                        lowerRight.getY() - upperLeft.getY()));
     196
     197                final Graphics2D g2d = (Graphics2D) g;
     198                g2d.setStroke(new BasicStroke(2));
     199            }
     200        }
     201    }
     202
     203    private Point img2compCoord(Rectangle visibleRect, int xImg, int yImg) {
    324204        Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
    325         visibleRect.x = mousePointInImg.x + ((drawRect.x - e.getX()) * visibleRect.width) / drawRect.width;
    326         visibleRect.y = mousePointInImg.y + ((drawRect.y - e.getY()) * visibleRect.height) / drawRect.height;
    327         // The position is also limited by the image size
    328         checkVisibleRectPos(image, visibleRect);
    329         synchronized (StreetsideImageDisplay.this) {
    330           StreetsideImageDisplay.this.visibleRect = visibleRect;
    331         }
    332         StreetsideImageDisplay.this.repaint();
    333       }
    334     }
    335 
    336     /**
    337      * Center the display on the point that has been clicked
    338      */
    339     @Override
    340     public void mouseClicked(MouseEvent e) {
    341       // Move the center to the clicked point.
    342       Image image;
    343       Rectangle visibleRect;
    344       synchronized (StreetsideImageDisplay.this) {
    345         image = getImage();
    346         visibleRect = StreetsideImageDisplay.this.visibleRect;
    347       }
    348       if (image != null && Math.min(getSize().getWidth(), getSize().getHeight()) > 0) {
    349         if (e.getButton() == StreetsideProperties.PICTURE_OPTION_BUTTON.get()) {
    350           if (!StreetsideImageDisplay.this.visibleRect
    351               .equals(new Rectangle(0, 0, image.getWidth(null), image.getHeight(null)))) {
    352             // Zooms to 1:1
    353             StreetsideImageDisplay.this.visibleRect = new Rectangle(0, 0, image.getWidth(null),
    354                 image.getHeight(null));
    355           } else {
    356             // Zooms to best fit.
    357             StreetsideImageDisplay.this.visibleRect = new Rectangle(0,
    358                 (image.getHeight(null) - (image.getWidth(null) * getHeight()) / getWidth()) / 2,
    359                 image.getWidth(null), (image.getWidth(null) * getHeight()) / getWidth());
    360           }
    361           StreetsideImageDisplay.this.repaint();
    362           return;
    363         } else if (e.getButton() != StreetsideProperties.PICTURE_DRAG_BUTTON.get()) {
    364           return;
    365         }
    366         // Calculate the translation to set the clicked point the center of
    367         // the view.
    368         Point click = comp2imgCoord(visibleRect, e.getX(), e.getY());
    369         Point center = getCenterImgCoord(visibleRect);
    370         visibleRect.x += click.x - center.x;
    371         visibleRect.y += click.y - center.y;
    372         checkVisibleRectPos(image, visibleRect);
    373         synchronized (StreetsideImageDisplay.this) {
    374           StreetsideImageDisplay.this.visibleRect = visibleRect;
    375         }
    376         StreetsideImageDisplay.this.repaint();
    377       }
    378     }
    379 
    380     /**
    381      * Initialize the dragging, either with button 1 (simple dragging) or button
    382      * 3 (selection of a picture part)
    383      */
    384     @Override
    385     public void mousePressed(MouseEvent e) {
    386       if (getImage() == null) {
    387         mouseIsDragging = false;
    388         selectedRect = null;
    389         return;
    390       }
    391       Image image;
    392       Rectangle visibleRect;
    393       synchronized (StreetsideImageDisplay.this) {
    394         image = StreetsideImageDisplay.this.image;
    395         visibleRect = StreetsideImageDisplay.this.visibleRect;
    396       }
    397       if (image == null)
    398         return;
    399       if (e.getButton() == StreetsideProperties.PICTURE_DRAG_BUTTON.get()) {
    400         mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
    401         mouseIsDragging = true;
    402         selectedRect = null;
    403       } else if (e.getButton() == StreetsideProperties.PICTURE_ZOOM_BUTTON.get()) {
    404         mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
    405         checkPointInVisibleRect(mousePointInImg, visibleRect);
    406         mouseIsDragging = false;
    407         selectedRect = new Rectangle(mousePointInImg.x, mousePointInImg.y, 0, 0);
    408         StreetsideImageDisplay.this.repaint();
    409       } else {
    410         mouseIsDragging = false;
    411         selectedRect = null;
    412       }
    413     }
    414 
    415     @Override
    416     public void mouseDragged(MouseEvent e) {
    417       if (!mouseIsDragging && selectedRect == null)
    418         return;
    419       Image image;
    420       Rectangle visibleRect;
    421       synchronized (StreetsideImageDisplay.this) {
    422         image = getImage();
    423         visibleRect = StreetsideImageDisplay.this.visibleRect;
    424       }
    425       if (image == null) {
    426         mouseIsDragging = false;
    427         selectedRect = null;
    428         return;
    429       }
    430       if (mouseIsDragging) {
    431         Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
    432         visibleRect.x += mousePointInImg.x - p.x;
    433         visibleRect.y += mousePointInImg.y - p.y;
    434         checkVisibleRectPos(image, visibleRect);
    435         synchronized (StreetsideImageDisplay.this) {
    436           StreetsideImageDisplay.this.visibleRect = visibleRect;
    437         }
    438         StreetsideImageDisplay.this.repaint();
    439       } else if (selectedRect != null) {
    440         Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
    441         checkPointInVisibleRect(p, visibleRect);
    442         Rectangle rect = new Rectangle(p.x < mousePointInImg.x ? p.x : mousePointInImg.x,
    443             p.y < mousePointInImg.y ? p.y : mousePointInImg.y,
    444             p.x < mousePointInImg.x ? mousePointInImg.x - p.x : p.x - mousePointInImg.x,
    445             p.y < mousePointInImg.y ? mousePointInImg.y - p.y : p.y - mousePointInImg.y);
    446         checkVisibleRectSize(image, rect);
    447         checkVisibleRectPos(image, rect);
    448         selectedRect = rect;
    449         StreetsideImageDisplay.this.repaint();
    450       }
    451     }
    452 
    453     @Override
    454     public void mouseReleased(MouseEvent e) {
    455       if (!mouseIsDragging && selectedRect == null)
    456         return;
    457       Image image;
    458       synchronized (StreetsideImageDisplay.this) {
    459         image = getImage();
    460       }
    461       if (image == null) {
    462         mouseIsDragging = false;
    463         selectedRect = null;
    464         return;
    465       }
    466       if (mouseIsDragging) {
    467         mouseIsDragging = false;
    468       } else if (selectedRect != null) {
    469         int oldWidth = selectedRect.width;
    470         int oldHeight = selectedRect.height;
    471         // Check that the zoom doesn't exceed 2:1
    472         if (selectedRect.width < getSize().width / 2) {
    473           selectedRect.width = getSize().width / 2;
    474         }
    475         if (selectedRect.height < getSize().height / 2) {
    476           selectedRect.height = getSize().height / 2;
    477         }
    478         // Set the same ratio for the visible rectangle and the display
    479         // area
    480         int hFact = selectedRect.height * getSize().width;
    481         int wFact = selectedRect.width * getSize().height;
    482         if (hFact > wFact) {
    483           selectedRect.width = hFact / getSize().height;
    484         } else {
    485           selectedRect.height = wFact / getSize().width;
    486         }
    487         // Keep the center of the selection
    488         if (selectedRect.width != oldWidth) {
    489           selectedRect.x -= (selectedRect.width - oldWidth) / 2;
    490         }
    491         if (selectedRect.height != oldHeight) {
    492           selectedRect.y -= (selectedRect.height - oldHeight) / 2;
    493         }
    494         checkVisibleRectSize(image, selectedRect);
    495         checkVisibleRectPos(image, selectedRect);
    496         synchronized (StreetsideImageDisplay.this) {
    497           visibleRect = selectedRect;
    498         }
    499         selectedRect = null;
    500         StreetsideImageDisplay.this.repaint();
    501       }
    502     }
    503 
    504     @Override
    505     public void mouseEntered(MouseEvent e) {
    506       // Do nothing, method is enforced by MouseListener
    507     }
    508 
    509     @Override
    510     public void mouseExited(MouseEvent e) {
    511       // Do nothing, method is enforced by MouseListener
    512     }
    513 
    514     @Override
    515     public void mouseMoved(MouseEvent e) {
    516       // Do nothing, method is enforced by MouseListener
    517     }
    518 
    519     private void checkPointInVisibleRect(Point p, Rectangle visibleRect) {
    520       if (p.x < visibleRect.x) {
    521         p.x = visibleRect.x;
    522       }
    523       if (p.x > visibleRect.x + visibleRect.width) {
    524         p.x = visibleRect.x + visibleRect.width;
    525       }
    526       if (p.y < visibleRect.y) {
    527         p.y = visibleRect.y;
    528       }
    529       if (p.y > visibleRect.y + visibleRect.height) {
    530         p.y = visibleRect.y + visibleRect.height;
    531       }
    532     }
    533   }
     205        return new Point(drawRect.x + ((xImg - visibleRect.x) * drawRect.width) / visibleRect.width,
     206                drawRect.y + ((yImg - visibleRect.y) * drawRect.height) / visibleRect.height);
     207    }
     208
     209    private Point comp2imgCoord(Rectangle visibleRect, int xComp, int yComp) {
     210        Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
     211        return new Point(visibleRect.x + ((xComp - drawRect.x) * visibleRect.width) / drawRect.width,
     212                visibleRect.y + ((yComp - drawRect.y) * visibleRect.height) / drawRect.height);
     213    }
     214
     215    private Rectangle calculateDrawImageRectangle(Rectangle visibleRect) {
     216        return calculateDrawImageRectangle(visibleRect, new Rectangle(0, 0, getSize().width, getSize().height));
     217    }
     218
     219    private class ImgDisplayMouseListener implements MouseListener, MouseWheelListener, MouseMotionListener {
     220        private boolean mouseIsDragging;
     221        private long lastTimeForMousePoint;
     222        private Point mousePointInImg;
     223
     224        /**
     225         * Zoom in and out, trying to preserve the point of the image that was under
     226         * the mouse cursor at the same place
     227         */
     228        @Override
     229        public void mouseWheelMoved(MouseWheelEvent e) {
     230            Image image;
     231            Rectangle visibleRect;
     232            synchronized (StreetsideImageDisplay.this) {
     233                image = getImage();
     234                visibleRect = StreetsideImageDisplay.this.visibleRect;
     235            }
     236            mouseIsDragging = false;
     237            selectedRect = null;
     238            if (image != null && Math.min(getSize().getWidth(), getSize().getHeight()) > 0) {
     239                // Calculate the mouse cursor position in image coordinates, so that
     240                // we can center the zoom
     241                // on that mouse position.
     242                // To avoid issues when the user tries to zoom in on the image
     243                // borders, this point is not calculated
     244                // again if there was less than 1.5seconds since the last event.
     245                if (e.getWhen() - lastTimeForMousePoint > 1500 || mousePointInImg == null) {
     246                    lastTimeForMousePoint = e.getWhen();
     247                    mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
     248                }
     249                // Set the zoom to the visible rectangle in image coordinates
     250                if (e.getWheelRotation() > 0) {
     251                    visibleRect.width = visibleRect.width * 3 / 2;
     252                    visibleRect.height = visibleRect.height * 3 / 2;
     253                } else {
     254                    visibleRect.width = visibleRect.width * 2 / 3;
     255                    visibleRect.height = visibleRect.height * 2 / 3;
     256                }
     257                // Check that the zoom doesn't exceed 2:1
     258                if (visibleRect.width < getSize().width / 2) {
     259                    visibleRect.width = getSize().width / 2;
     260                }
     261                if (visibleRect.height < getSize().height / 2) {
     262                    visibleRect.height = getSize().height / 2;
     263                }
     264                // Set the same ratio for the visible rectangle and the display area
     265                int hFact = visibleRect.height * getSize().width;
     266                int wFact = visibleRect.width * getSize().height;
     267                if (hFact > wFact) {
     268                    visibleRect.width = hFact / getSize().height;
     269                } else {
     270                    visibleRect.height = wFact / getSize().width;
     271                }
     272                // The size of the visible rectangle is limited by the image size.
     273                checkVisibleRectSize(image, visibleRect);
     274                // Set the position of the visible rectangle, so that the mouse
     275                // cursor doesn't move on the image.
     276                Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
     277                visibleRect.x = mousePointInImg.x + ((drawRect.x - e.getX()) * visibleRect.width) / drawRect.width;
     278                visibleRect.y = mousePointInImg.y + ((drawRect.y - e.getY()) * visibleRect.height) / drawRect.height;
     279                // The position is also limited by the image size
     280                checkVisibleRectPos(image, visibleRect);
     281                synchronized (StreetsideImageDisplay.this) {
     282                    StreetsideImageDisplay.this.visibleRect = visibleRect;
     283                }
     284                StreetsideImageDisplay.this.repaint();
     285            }
     286        }
     287
     288        /**
     289         * Center the display on the point that has been clicked
     290         */
     291        @Override
     292        public void mouseClicked(MouseEvent e) {
     293            // Move the center to the clicked point.
     294            Image image;
     295            Rectangle visibleRect;
     296            synchronized (StreetsideImageDisplay.this) {
     297                image = getImage();
     298                visibleRect = StreetsideImageDisplay.this.visibleRect;
     299            }
     300            if (image != null && Math.min(getSize().getWidth(), getSize().getHeight()) > 0) {
     301                if (e.getButton() == StreetsideProperties.PICTURE_OPTION_BUTTON.get()) {
     302                    if (!StreetsideImageDisplay.this.visibleRect
     303                            .equals(new Rectangle(0, 0, image.getWidth(null), image.getHeight(null)))) {
     304                        // Zooms to 1:1
     305                        StreetsideImageDisplay.this.visibleRect = new Rectangle(0, 0, image.getWidth(null),
     306                                image.getHeight(null));
     307                    } else {
     308                        // Zooms to best fit.
     309                        StreetsideImageDisplay.this.visibleRect = new Rectangle(0,
     310                                (image.getHeight(null) - (image.getWidth(null) * getHeight()) / getWidth()) / 2,
     311                                image.getWidth(null), (image.getWidth(null) * getHeight()) / getWidth());
     312                    }
     313                    StreetsideImageDisplay.this.repaint();
     314                    return;
     315                } else if (e.getButton() != StreetsideProperties.PICTURE_DRAG_BUTTON.get()) {
     316                    return;
     317                }
     318                // Calculate the translation to set the clicked point the center of
     319                // the view.
     320                Point click = comp2imgCoord(visibleRect, e.getX(), e.getY());
     321                Point center = getCenterImgCoord(visibleRect);
     322                visibleRect.x += click.x - center.x;
     323                visibleRect.y += click.y - center.y;
     324                checkVisibleRectPos(image, visibleRect);
     325                synchronized (StreetsideImageDisplay.this) {
     326                    StreetsideImageDisplay.this.visibleRect = visibleRect;
     327                }
     328                StreetsideImageDisplay.this.repaint();
     329            }
     330        }
     331
     332        /**
     333         * Initialize the dragging, either with button 1 (simple dragging) or button
     334         * 3 (selection of a picture part)
     335         */
     336        @Override
     337        public void mousePressed(MouseEvent e) {
     338            if (getImage() == null) {
     339                mouseIsDragging = false;
     340                selectedRect = null;
     341                return;
     342            }
     343            Image image;
     344            Rectangle visibleRect;
     345            synchronized (StreetsideImageDisplay.this) {
     346                image = StreetsideImageDisplay.this.image;
     347                visibleRect = StreetsideImageDisplay.this.visibleRect;
     348            }
     349            if (image == null)
     350                return;
     351            if (e.getButton() == StreetsideProperties.PICTURE_DRAG_BUTTON.get()) {
     352                mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
     353                mouseIsDragging = true;
     354                selectedRect = null;
     355            } else if (e.getButton() == StreetsideProperties.PICTURE_ZOOM_BUTTON.get()) {
     356                mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
     357                checkPointInVisibleRect(mousePointInImg, visibleRect);
     358                mouseIsDragging = false;
     359                selectedRect = new Rectangle(mousePointInImg.x, mousePointInImg.y, 0, 0);
     360                StreetsideImageDisplay.this.repaint();
     361            } else {
     362                mouseIsDragging = false;
     363                selectedRect = null;
     364            }
     365        }
     366
     367        @Override
     368        public void mouseDragged(MouseEvent e) {
     369            if (!mouseIsDragging && selectedRect == null)
     370                return;
     371            Image image;
     372            Rectangle visibleRect;
     373            synchronized (StreetsideImageDisplay.this) {
     374                image = getImage();
     375                visibleRect = StreetsideImageDisplay.this.visibleRect;
     376            }
     377            if (image == null) {
     378                mouseIsDragging = false;
     379                selectedRect = null;
     380                return;
     381            }
     382            if (mouseIsDragging) {
     383                Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
     384                visibleRect.x += mousePointInImg.x - p.x;
     385                visibleRect.y += mousePointInImg.y - p.y;
     386                checkVisibleRectPos(image, visibleRect);
     387                synchronized (StreetsideImageDisplay.this) {
     388                    StreetsideImageDisplay.this.visibleRect = visibleRect;
     389                }
     390                StreetsideImageDisplay.this.repaint();
     391            } else if (selectedRect != null) {
     392                Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
     393                checkPointInVisibleRect(p, visibleRect);
     394                Rectangle rect = new Rectangle(Math.min(p.x, mousePointInImg.x), Math.min(p.y, mousePointInImg.y),
     395                        p.x < mousePointInImg.x ? mousePointInImg.x - p.x : p.x - mousePointInImg.x,
     396                        p.y < mousePointInImg.y ? mousePointInImg.y - p.y : p.y - mousePointInImg.y);
     397                checkVisibleRectSize(image, rect);
     398                checkVisibleRectPos(image, rect);
     399                selectedRect = rect;
     400                StreetsideImageDisplay.this.repaint();
     401            }
     402        }
     403
     404        @Override
     405        public void mouseReleased(MouseEvent e) {
     406            if (!mouseIsDragging && selectedRect == null)
     407                return;
     408            Image image;
     409            synchronized (StreetsideImageDisplay.this) {
     410                image = getImage();
     411            }
     412            if (image == null) {
     413                mouseIsDragging = false;
     414                selectedRect = null;
     415                return;
     416            }
     417            if (mouseIsDragging) {
     418                mouseIsDragging = false;
     419            } else if (selectedRect != null) {
     420                int oldWidth = selectedRect.width;
     421                int oldHeight = selectedRect.height;
     422                // Check that the zoom doesn't exceed 2:1
     423                if (selectedRect.width < getSize().width / 2) {
     424                    selectedRect.width = getSize().width / 2;
     425                }
     426                if (selectedRect.height < getSize().height / 2) {
     427                    selectedRect.height = getSize().height / 2;
     428                }
     429                // Set the same ratio for the visible rectangle and the display
     430                // area
     431                int hFact = selectedRect.height * getSize().width;
     432                int wFact = selectedRect.width * getSize().height;
     433                if (hFact > wFact) {
     434                    selectedRect.width = hFact / getSize().height;
     435                } else {
     436                    selectedRect.height = wFact / getSize().width;
     437                }
     438                // Keep the center of the selection
     439                if (selectedRect.width != oldWidth) {
     440                    selectedRect.x -= (selectedRect.width - oldWidth) / 2;
     441                }
     442                if (selectedRect.height != oldHeight) {
     443                    selectedRect.y -= (selectedRect.height - oldHeight) / 2;
     444                }
     445                checkVisibleRectSize(image, selectedRect);
     446                checkVisibleRectPos(image, selectedRect);
     447                synchronized (StreetsideImageDisplay.this) {
     448                    visibleRect = selectedRect;
     449                }
     450                selectedRect = null;
     451                StreetsideImageDisplay.this.repaint();
     452            }
     453        }
     454
     455        @Override
     456        public void mouseEntered(MouseEvent e) {
     457            // Do nothing, method is enforced by MouseListener
     458        }
     459
     460        @Override
     461        public void mouseExited(MouseEvent e) {
     462            // Do nothing, method is enforced by MouseListener
     463        }
     464
     465        @Override
     466        public void mouseMoved(MouseEvent e) {
     467            // Do nothing, method is enforced by MouseListener
     468        }
     469
     470        private void checkPointInVisibleRect(Point p, Rectangle visibleRect) {
     471            if (p.x < visibleRect.x) {
     472                p.x = visibleRect.x;
     473            }
     474            if (p.x > visibleRect.x + visibleRect.width) {
     475                p.x = visibleRect.x + visibleRect.width;
     476            }
     477            if (p.y < visibleRect.y) {
     478                p.y = visibleRect.y;
     479            }
     480            if (p.y > visibleRect.y + visibleRect.height) {
     481                p.y = visibleRect.y + visibleRect.height;
     482            }
     483        }
     484    }
    534485}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsideMainDialog.java

    r36194 r36228  
    99import java.io.ByteArrayInputStream;
    1010import java.io.IOException;
     11import java.io.Serial;
    1112import java.text.MessageFormat;
    1213import java.util.Arrays;
     14import java.util.HashMap;
    1315import java.util.List;
     16import java.util.Map;
     17import java.util.Optional;
    1418import java.util.logging.Logger;
     19import java.util.stream.Collectors;
    1520
    1621import javax.imageio.ImageIO;
     
    2126import javax.swing.SwingUtilities;
    2227
     28import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
    2329import org.openstreetmap.josm.data.cache.CacheEntry;
    2430import org.openstreetmap.josm.data.cache.CacheEntryAttributes;
     
    2632import org.openstreetmap.josm.gui.SideButton;
    2733import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
     34import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY;
    2835import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    2936import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener;
     
    3441import org.openstreetmap.josm.plugins.streetside.actions.WalkThread;
    3542import org.openstreetmap.josm.plugins.streetside.cache.StreetsideCache;
     43import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils;
    3644import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.ImageInfoHelpPopup;
     45import org.openstreetmap.josm.plugins.streetside.utils.GraphicsUtils;
    3746import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
    3847import org.openstreetmap.josm.tools.I18n;
     
    4857public final class StreetsideMainDialog extends ToggleDialog implements ICachedLoaderListener, StreetsideDataListener {
    4958
    50   public static final String BASE_TITLE = I18n.marktr("Microsoft Streetside image");
    51   private static final long serialVersionUID = 2645654786827812861L;
    52   private static final Logger LOGGER = Logger.getLogger(StreetsideMainDialog.class.getCanonicalName());
    53   private static final String MESSAGE_SEPARATOR = " — ";
    54 
    55   private static StreetsideMainDialog instance;
    56   /**
    57    * Button used to jump to the image following the red line
    58    */
    59   public final SideButton redButton = new SideButton(new RedAction());
    60   /**
    61    * Button used to jump to the image following the blue line
    62    */
    63   public final SideButton blueButton = new SideButton(new BlueAction());
    64   private final SideButton nextButton = new SideButton(new NextPictureAction());
    65   private final SideButton previousButton = new SideButton(new PreviousPictureAction());
    66   private final SideButton playButton = new SideButton(new PlayAction());
    67   private final SideButton pauseButton = new SideButton(new PauseAction());
    68   private final SideButton stopButton = new SideButton(new StopAction());
    69   /**
    70    * Object containing the shown image and that handles zoom and drag
    71    */
    72   public StreetsideImageDisplay streetsideImageDisplay;
    73   public StreetsideCache thumbnailCache;
    74   private volatile StreetsideAbstractImage image;
    75   private ImageInfoHelpPopup imageInfoHelp;
    76   private StreetsideCache imageCache;
    77 
    78   private StreetsideMainDialog() {
    79     super(I18n.tr(StreetsideMainDialog.BASE_TITLE), "streetside-main", I18n.tr("Open Streetside window"), null, 200,
    80         true, StreetsidePreferenceSetting.class);
    81     addShortcuts();
    82 
    83     streetsideImageDisplay = new StreetsideImageDisplay();
    84 
    85     blueButton.setForeground(Color.BLUE);
    86     redButton.setForeground(Color.RED);
    87 
    88     setMode(MODE.NORMAL);
    89   }
    90 
    91   /**
    92    * Returns the unique instance of the class.
    93    *
    94    * @return The unique instance of the class.
    95    */
    96   public static synchronized StreetsideMainDialog getInstance() {
    97     if (StreetsideMainDialog.instance == null) {
    98       StreetsideMainDialog.instance = new StreetsideMainDialog();
    99     }
    100     return StreetsideMainDialog.instance;
    101   }
    102 
    103   /**
    104    * @return true, iff the singleton instance is present
    105    */
    106   public static boolean hasInstance() {
    107     return StreetsideMainDialog.instance != null;
    108   }
    109 
    110   /**
    111    * Destroys the unique instance of the class.
    112    */
    113   public static synchronized void destroyInstance() {
    114     StreetsideMainDialog.instance = null;
    115   }
    116 
    117   /**
    118    * Adds the shortcuts to the buttons.
    119    */
    120   private void addShortcuts() {
    121     nextButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("PAGE_DOWN"), "next");
    122     nextButton.getActionMap().put("next", new NextPictureAction());
    123     previousButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("PAGE_UP"),
    124         "previous");
    125     previousButton.getActionMap().put("previous", new PreviousPictureAction());
    126     blueButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control PAGE_UP"),
    127         "blue");
    128     blueButton.getActionMap().put("blue", new BlueAction());
    129     redButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control PAGE_DOWN"),
    130         "red");
    131     redButton.getActionMap().put("red", new RedAction());
    132   }
    133 
    134   public synchronized void setImageInfoHelp(ImageInfoHelpPopup popup) {
    135     imageInfoHelp = popup;
    136   }
    137 
    138   /**
    139    * Sets a new mode for the dialog.
    140    *
    141    * @param mode The mode to be set. Must not be {@code null}.
    142    */
    143   public void setMode(MODE mode) {
    144     switch (mode) {
    145     case WALK:
    146       createLayout(streetsideImageDisplay, Arrays.asList(playButton, pauseButton, stopButton));
    147       break;
    148     case NORMAL:
    149     default:
    150       createLayout(streetsideImageDisplay, Arrays.asList(blueButton, previousButton, nextButton, redButton));
    151       break;
    152     }
    153     disableAllButtons();
    154     if (MODE.NORMAL == mode) {
    155       updateImage();
    156     }
    157     revalidate();
    158     repaint();
    159   }
    160 
    161   /**
    162    * Downloads the full quality picture of the selected StreetsideImage and sets
    163    * in the StreetsideImageDisplay object.
    164    */
    165   public synchronized void updateImage() {
    166     updateImage(true);
    167   }
    168 
    169   /**
    170    * Downloads the picture of the selected StreetsideImage and sets in the
    171    * StreetsideImageDisplay object.
    172    *
    173    * @param fullQuality If the full quality picture must be downloaded or just the
    174    *          thumbnail.
    175    */
    176   public synchronized void updateImage(boolean fullQuality) {
    177     if (!SwingUtilities.isEventDispatchThread()) {
    178       SwingUtilities.invokeLater(this::updateImage);
    179     } else {
    180       if (!StreetsideLayer.hasInstance()) {
    181         return;
    182       }
    183       if (image == null) {
    184         streetsideImageDisplay.setImage(null, null);
    185         setTitle(I18n.tr(StreetsideMainDialog.BASE_TITLE));
    186         return;
    187       }
    188 
    189       if (imageInfoHelp != null && StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN.get() > 0
    190           && imageInfoHelp.showPopup()) {
    191         // Count down the number of times the popup will be displayed
    192         StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN
    193             .put(StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN.get() - 1);
    194       }
    195 
    196       if (image instanceof StreetsideImage) {
    197         final StreetsideImage streetsideImage = (StreetsideImage) image;
    198         // Downloads the thumbnail.
    199         streetsideImageDisplay.setImage(null, null);
    200         if (thumbnailCache != null) {
    201           thumbnailCache.cancelOutstandingTasks();
    202         }
    203         thumbnailCache = new StreetsideCache(streetsideImage.getId(), StreetsideCache.Type.THUMBNAIL);
    204         try {
    205           thumbnailCache.submit(this, false);
    206         } catch (final IOException e) {
    207           LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
    208         }
    209 
    210         // Downloads the full resolution image.
    211         if (fullQuality || new StreetsideCache(streetsideImage.getId(), StreetsideCache.Type.FULL_IMAGE)
    212             .get() != null) {
    213           if (imageCache != null) {
    214             imageCache.cancelOutstandingTasks();
    215           }
    216           imageCache = new StreetsideCache(streetsideImage.getId(), StreetsideCache.Type.FULL_IMAGE);
    217           try {
    218             imageCache.submit(this, false);
    219           } catch (final IOException e) {
    220             LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
    221           }
    222         }
    223       }
    224       updateTitle();
    225     }
    226   }
    227 
    228   /**
    229    * Disables all the buttons in the dialog
    230    */
    231   private void disableAllButtons() {
    232     nextButton.setEnabled(false);
    233     previousButton.setEnabled(false);
    234     blueButton.setEnabled(false);
    235     redButton.setEnabled(false);
    236   }
    237 
    238   /**
    239    * Updates the title of the dialog.
    240    */
    241   public synchronized void updateTitle() {
    242     if (!SwingUtilities.isEventDispatchThread()) {
    243       SwingUtilities.invokeLater(this::updateTitle);
    244     } else if (image != null) {
    245       final StringBuilder title = new StringBuilder(I18n.tr(StreetsideMainDialog.BASE_TITLE));
    246       if (image instanceof StreetsideImage) {
    247         title.append(StreetsideMainDialog.MESSAGE_SEPARATOR)
    248             .append(MessageFormat.format("(heading {0}°)", Double.toString(image.getHe())));
    249         setTitle(title.toString());
    250       }
    251     }
    252   }
    253 
    254   /**
    255    * Returns the {@link StreetsideAbstractImage} object which is being shown.
    256    *
    257    * @return The {@link StreetsideAbstractImage} object which is being shown.
    258    */
    259   public synchronized StreetsideAbstractImage getImage() {
    260     return image;
    261   }
    262 
    263   /**
    264    * Sets a new StreetsideImage to be shown.
    265    *
    266    * @param image The image to be shown.
    267    */
    268   public synchronized void setImage(StreetsideAbstractImage image) {
    269     this.image = image;
    270   }
    271 
    272   /**
    273    * When the pictures are returned from the cache, they are set in the
    274    * {@link StreetsideImageDisplay} object.
    275    */
    276   @Override
    277   public void loadingFinished(final CacheEntry data, final CacheEntryAttributes attributes, final LoadResult result) {
    278     if (!SwingUtilities.isEventDispatchThread()) {
    279       SwingUtilities.invokeLater(() -> loadingFinished(data, attributes, result));
    280 
    281     } else if (data != null && result == LoadResult.SUCCESS) {
    282       try {
    283         final BufferedImage img = ImageIO.read(new ByteArrayInputStream(data.getContent()));
    284         if (img == null) {
    285           return;
    286         }
    287         if (streetsideImageDisplay.getImage() == null
    288             || img.getHeight() > streetsideImageDisplay.getImage().getHeight()) {
    289           streetsideImageDisplay.setImage(img, null);
    290         }
    291       } catch (final IOException e) {
    292         LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
    293       }
    294     }
    295   }
    296 
    297   /**
    298    * Creates the layout of the dialog.
    299    *
    300    * @param data  The content of the dialog
    301    * @param buttons The buttons where you can click
    302    */
    303   public void createLayout(Component data, List<SideButton> buttons) {
    304     removeAll();
    305     createLayout(data, true, buttons);
    306     add(titleBar, BorderLayout.NORTH);
    307   }
    308 
    309   @Override
    310   public void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) {
    311     setImage(newImage);
    312     if (newImage != null && newImage.getSequence() != null) {
    313       if (newImage.next() != null) {
    314         nextButton.setEnabled(true);
    315       }
    316       if (newImage.previous() != null) {
    317         previousButton.setEnabled(true);
    318       }
    319     }
    320     updateImage();
    321   }
    322 
    323   @Override
    324   public void imagesAdded() {
    325     // This method is enforced by StreetsideDataListener, but only selectedImageChanged() is needed
    326   }
    327 
    328   /**
    329    * @return the streetsideImageDisplay
    330    */
    331   public StreetsideImageDisplay getStreetsideImageDisplay() {
    332     return streetsideImageDisplay;
    333   }
    334 
    335   /**
    336    * @param streetsideImageDisplay the streetsideImageDisplay to set
    337    */
    338   public void setStreetsideImageDisplay(StreetsideImageDisplay streetsideImageDisplay) {
    339     this.streetsideImageDisplay = streetsideImageDisplay;
    340   }
    341 
    342   /**
    343    * Buttons mode.
    344    *
    345    * @author nokutu
    346    */
    347   public enum MODE {
    348     /**
    349      * Standard mode to view pictures.
    350      */
    351     NORMAL,
    352     /**
    353      * Mode when in walk.
    354      */
    355     WALK
    356   }
    357 
    358   /**
    359    * Action class form the next image button.
    360    *
    361    * @author nokutu
    362    */
    363   private static class NextPictureAction extends AbstractAction {
    364 
    365     private static final long serialVersionUID = 6333692154558730392L;
    366 
    367     /**
    368      * Constructs a normal NextPictureAction
    369      */
    370     NextPictureAction() {
    371       super(I18n.tr("Next picture"));
    372       putValue(Action.SHORT_DESCRIPTION, I18n.tr("Shows the next picture in the sequence"));
    373       new ImageProvider("help", "next").getResource().attachImageIcon(this, true);
    374     }
    375 
     59    public static final String BASE_TITLE = I18n.marktr("Microsoft Streetside image");
     60    @Serial
     61    private static final long serialVersionUID = 2645654786827812861L;
     62    private static final Logger LOGGER = Logger.getLogger(StreetsideMainDialog.class.getCanonicalName());
     63    private static final String MESSAGE_SEPARATOR = " — ";
     64
     65    private static StreetsideMainDialog instance;
     66    /**
     67     * Button used to jump to the image following the red line
     68     */
     69    public final SideButton redButton = new SideButton(new RedAction());
     70    /**
     71     * Button used to jump to the image following the blue line
     72     */
     73    public final SideButton blueButton = new SideButton(new BlueAction());
     74    private final SideButton nextButton = new SideButton(new NextPictureAction());
     75    private final SideButton previousButton = new SideButton(new PreviousPictureAction());
     76    private final SideButton playButton = new SideButton(new PlayAction());
     77    private final SideButton pauseButton = new SideButton(new PauseAction());
     78    private final SideButton stopButton = new SideButton(new StopAction());
     79    /**
     80     * Object containing the shown image and that handles zoom and drag
     81     */
     82    public final StreetsideImageDisplay streetsideImageDisplay;
     83    public StreetsideCache thumbnailCache;
     84    private StreetsideAbstractImage image;
     85    /** Used to avoid  rerendering images multiple times (once per tile, effectively) */
     86    private long lastRenderedImageHash;
     87    private ImageInfoHelpPopup imageInfoHelp;
     88    private final Map<CubeMapTileXY, StreetsideCache> imageCache = new HashMap<>();
     89
     90    private StreetsideMainDialog() {
     91        super(I18n.tr(StreetsideMainDialog.BASE_TITLE), "streetside-main", I18n.tr("Open Streetside window"), null, 200,
     92                true, StreetsidePreferenceSetting.class);
     93        addShortcuts();
     94
     95        streetsideImageDisplay = new StreetsideImageDisplay();
     96
     97        blueButton.setForeground(Color.BLUE);
     98        redButton.setForeground(Color.RED);
     99
     100        setMode(MODE.NORMAL);
     101    }
     102
     103    /**
     104     * Returns the unique instance of the class.
     105     *
     106     * @return The unique instance of the class.
     107     */
     108    public static synchronized StreetsideMainDialog getInstance() {
     109        if (StreetsideMainDialog.instance == null) {
     110            StreetsideMainDialog.instance = new StreetsideMainDialog();
     111        }
     112        return StreetsideMainDialog.instance;
     113    }
     114
     115    /**
     116     * Check if there is an instance
     117     * @return true, iff the singleton instance is present
     118     */
     119    public static boolean hasInstance() {
     120        return StreetsideMainDialog.instance != null;
     121    }
     122
     123    /**
     124     * Destroys the unique instance of the class.
     125     */
     126    public static synchronized void destroyInstance() {
     127        StreetsideMainDialog.instance = null;
     128    }
     129
     130    /**
     131     * Adds the shortcuts to the buttons.
     132     */
     133    private void addShortcuts() {
     134        nextButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("PAGE_DOWN"), "next");
     135        nextButton.getActionMap().put("next", new NextPictureAction());
     136        previousButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("PAGE_UP"),
     137                "previous");
     138        previousButton.getActionMap().put("previous", new PreviousPictureAction());
     139        blueButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control PAGE_UP"),
     140                "blue");
     141        blueButton.getActionMap().put("blue", new BlueAction());
     142        redButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control PAGE_DOWN"),
     143                "red");
     144        redButton.getActionMap().put("red", new RedAction());
     145    }
     146
     147    public synchronized void setImageInfoHelp(ImageInfoHelpPopup popup) {
     148        imageInfoHelp = popup;
     149    }
     150
     151    /**
     152     * Sets a new mode for the dialog.
     153     *
     154     * @param mode The mode to be set. Must not be {@code null}.
     155     */
     156    public void setMode(MODE mode) {
     157        switch (mode) {
     158        case WALK:
     159            createLayout(streetsideImageDisplay, Arrays.asList(playButton, pauseButton, stopButton));
     160            break;
     161        case NORMAL:
     162        default:
     163            createLayout(streetsideImageDisplay, Arrays.asList(blueButton, previousButton, nextButton, redButton));
     164            break;
     165        }
     166        disableAllButtons();
     167        if (MODE.NORMAL == mode) {
     168            updateImage();
     169        }
     170        revalidate();
     171        repaint();
     172    }
     173
     174    /**
     175     * Downloads the full quality picture of the selected StreetsideImage and sets
     176     * in the StreetsideImageDisplay object.
     177     */
     178    public synchronized void updateImage() {
     179        updateImage(true);
     180    }
     181
     182    /**
     183     * Downloads the picture of the selected StreetsideImage and sets in the
     184     * StreetsideImageDisplay object.
     185     *
     186     * @param fullQuality If the full quality picture must be downloaded or just the
     187     *          thumbnail.
     188     */
     189    public synchronized void updateImage(boolean fullQuality) {
     190        if (!SwingUtilities.isEventDispatchThread()) {
     191            SwingUtilities.invokeLater(this::updateImage);
     192        } else {
     193            if (!StreetsideLayer.hasInstance()) {
     194                return;
     195            }
     196            if (image == null) {
     197                streetsideImageDisplay.setImage(null);
     198                setTitle(I18n.tr(StreetsideMainDialog.BASE_TITLE));
     199                return;
     200            }
     201
     202            if (imageInfoHelp != null && StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN.get() > 0
     203                    && imageInfoHelp.showPopup()) {
     204                // Count down the number of times the popup will be displayed
     205                StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN
     206                        .put(StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN.get() - 1);
     207            }
     208
     209            if (image instanceof StreetsideImage streetsideImage) {
     210                // Downloads the thumbnail.
     211                streetsideImageDisplay.setImage(null);
     212                if (thumbnailCache != null) {
     213                    thumbnailCache.cancelOutstandingTasks();
     214                }
     215                thumbnailCache = new StreetsideCache(streetsideImage.getThumbnail());
     216                try {
     217                    thumbnailCache.submit(this, false);
     218                } catch (final IOException e) {
     219                    LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     220                }
     221
     222                // Downloads the full resolution image.
     223                if (fullQuality) {
     224                    synchronized (this.imageCache) {
     225                        this.imageCache.values().forEach(StreetsideCache::cancelOutstandingTasks);
     226                        this.imageCache.clear();
     227                        this.imageCache.putAll(
     228                                streetsideImage.getFaceTiles(CubemapUtils.CubemapFaces.FRONT, streetsideImage.zoomMax())
     229                                        .collect(Collectors.toMap(p -> p.a, p -> new StreetsideCache(p.b))));
     230                        for (StreetsideCache cache : this.imageCache.values()) {
     231                            try {
     232                                cache.submit(this, false);
     233                            } catch (final IOException e) {
     234                                LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     235                            }
     236                        }
     237                    }
     238                }
     239            }
     240            updateTitle();
     241        }
     242    }
     243
     244    /**
     245     * Disables all the buttons in the dialog
     246     */
     247    private void disableAllButtons() {
     248        nextButton.setEnabled(false);
     249        previousButton.setEnabled(false);
     250        blueButton.setEnabled(false);
     251        redButton.setEnabled(false);
     252    }
     253
     254    /**
     255     * Updates the title of the dialog.
     256     */
     257    public synchronized void updateTitle() {
     258        if (!SwingUtilities.isEventDispatchThread()) {
     259            SwingUtilities.invokeLater(this::updateTitle);
     260        } else if (image != null) {
     261            final var title = new StringBuilder(I18n.tr(StreetsideMainDialog.BASE_TITLE));
     262            if (image instanceof StreetsideImage) {
     263                title.append(StreetsideMainDialog.MESSAGE_SEPARATOR)
     264                        .append(MessageFormat.format("(heading {0}°)", Double.toString(image.heading())));
     265                setTitle(title.toString());
     266            }
     267        }
     268    }
     269
     270    /**
     271     * Returns the {@link StreetsideAbstractImage} object which is being shown.
     272     *
     273     * @return The {@link StreetsideAbstractImage} object which is being shown.
     274     */
     275    public synchronized StreetsideAbstractImage getImage() {
     276        return image;
     277    }
     278
     279    /**
     280     * Sets a new StreetsideImage to be shown.
     281     *
     282     * @param image The image to be shown.
     283     */
     284    public synchronized void setImage(StreetsideAbstractImage image) {
     285        this.image = image;
     286        this.lastRenderedImageHash = Long.MIN_VALUE;
     287    }
     288
     289    /**
     290     * When the pictures are returned from the cache, they are set in the
     291     * {@link StreetsideImageDisplay} object.
     292     */
    376293    @Override
    377     public void actionPerformed(ActionEvent e) {
    378       StreetsideLayer.getInstance().getData().selectNext();
    379     }
    380   }
    381 
    382   /**
    383    * Action class for the previous image button.
    384    *
    385    * @author nokutu
    386    */
    387   private static class PreviousPictureAction extends AbstractAction {
    388 
    389     private static final long serialVersionUID = 4390593660514657107L;
    390 
    391     /**
    392      * Constructs a normal PreviousPictureAction
    393      */
    394     PreviousPictureAction() {
    395       super(I18n.tr("Previous picture"));
    396       putValue(Action.SHORT_DESCRIPTION, I18n.tr("Shows the previous picture in the sequence"));
    397       new ImageProvider("help", "previous").getResource().attachImageIcon(this, true);
     294    public void loadingFinished(final CacheEntry data, final CacheEntryAttributes attributes, final LoadResult result) {
     295        if (!SwingUtilities.isEventDispatchThread()) {
     296            SwingUtilities.invokeLater(() -> loadingFinished(data, attributes, result));
     297
     298        } else if (data != null && result == LoadResult.SUCCESS) {
     299            try {
     300                final BufferedImage img = data instanceof BufferedImageCacheEntry bufferedData ? bufferedData.getImage()
     301                        : ImageIO.read(new ByteArrayInputStream(data.getContent()));
     302                if (img == null) {
     303                    return;
     304                }
     305                synchronized (this.imageCache) {
     306                    if (this.imageCache.isEmpty() || this.imageCache.size() == 1 || this.imageCache.containsKey(null)) {
     307                        streetsideImageDisplay.setImage(img);
     308                        return;
     309                    }
     310                    final var images = new HashMap<CubeMapTileXY, BufferedImage>(this.imageCache.size());
     311                    for (var entry : this.imageCache.entrySet()) {
     312                        images.put(entry.getKey(),
     313                                Optional.ofNullable(entry.getValue()).map(StreetsideCache::get).map(e -> {
     314                                    try {
     315                                        return e.getImage();
     316                                    } catch (IOException exception) {
     317                                        Logging.trace(exception);
     318                                    }
     319                                    return null;
     320                                }).orElse(null));
     321                    }
     322
     323                    if (images.containsValue(null)) {
     324                        return; // Not yet loaded, or something happened.
     325                    }
     326                    if (this.lastRenderedImageHash != images.hashCode()) {
     327                        this.lastRenderedImageHash = images.hashCode();
     328                        final var fullImage = GraphicsUtils.buildMultiTiledCubemapFaceImage(images,
     329                                (int) Math.round(Math.log(images.size()) / Math.log(4)));
     330                        streetsideImageDisplay.setImage(fullImage);
     331                    }
     332                }
     333            } catch (final IOException e) {
     334                LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     335            }
     336        }
     337    }
     338
     339    /**
     340     * Creates the layout of the dialog.
     341     *
     342     * @param data  The content of the dialog
     343     * @param buttons The buttons where you can click
     344     */
     345    public void createLayout(Component data, List<SideButton> buttons) {
     346        removeAll();
     347        createLayout(data, true, buttons);
     348        add(titleBar, BorderLayout.NORTH);
    398349    }
    399350
    400351    @Override
    401     public void actionPerformed(ActionEvent e) {
    402       StreetsideLayer.getInstance().getData().selectPrevious();
    403     }
    404   }
    405 
    406   /**
    407    * Action class to jump to the image following the red line.
    408    *
    409    * @author nokutu
    410    */
    411   private static class RedAction extends AbstractAction {
    412 
    413     private static final long serialVersionUID = -1244456062285831231L;
    414 
    415     /**
    416      * Constructs a normal RedAction
    417      */
    418     RedAction() {
    419       putValue(Action.NAME, I18n.tr("Jump to red"));
    420       putValue(Action.SHORT_DESCRIPTION, I18n.tr("Jumps to the picture at the other side of the red line"));
    421       new ImageProvider("dialogs", "red").getResource().attachImageIcon(this, true);
     352    public void selectedImageChanged(StreetsideImage oldImage, StreetsideImage newImage) {
     353        setImage(newImage);
     354        if (newImage != null) {
     355            if (StreetsideLayer.getInstance().getData().next(newImage) != null) {
     356                nextButton.setEnabled(true);
     357            }
     358            if (StreetsideLayer.getInstance().getData().previous(newImage) != null) {
     359                previousButton.setEnabled(true);
     360            }
     361        }
     362        updateImage();
    422363    }
    423364
    424365    @Override
    425     public void actionPerformed(ActionEvent e) {
    426       if (StreetsideMainDialog.getInstance().getImage() != null) {
    427         StreetsideLayer.getInstance().getData()
    428             .setSelectedImage(StreetsideLayer.getInstance().getNNearestImage(1), true);
    429       }
    430     }
    431   }
    432 
    433   /**
    434    * Action class to jump to the image following the blue line.
    435    *
    436    * @author nokutu
    437    */
    438   private static class BlueAction extends AbstractAction {
    439 
    440     private static final long serialVersionUID = 5951233534212838780L;
    441 
    442     /**
    443      * Constructs a normal BlueAction
    444      */
    445     BlueAction() {
    446       putValue(Action.NAME, I18n.tr("Jump to blue"));
    447       putValue(Action.SHORT_DESCRIPTION, I18n.tr("Jumps to the picture at the other side of the blue line"));
    448       new ImageProvider("dialogs", "blue").getResource().attachImageIcon(this, true);
    449     }
    450 
    451     @Override
    452     public void actionPerformed(ActionEvent e) {
    453       if (StreetsideMainDialog.getInstance().getImage() != null) {
    454         StreetsideLayer.getInstance().getData()
    455             .setSelectedImage(StreetsideLayer.getInstance().getNNearestImage(2), true);
    456       }
    457     }
    458   }
    459 
    460   private static class StopAction extends AbstractAction implements WalkListener {
    461 
    462     private static final long serialVersionUID = 8789972456611625341L;
    463 
    464     private WalkThread thread;
    465 
    466     /**
    467      * Constructs a normal StopAction
    468      */
    469     StopAction() {
    470       putValue(Action.NAME, I18n.tr("Stop"));
    471       putValue(Action.SHORT_DESCRIPTION, I18n.tr("Stops the walk."));
    472       new ImageProvider("dialogs/streetsideStop.png").getResource().attachImageIcon(this, true);
    473       StreetsidePlugin.getStreetsideWalkAction().addListener(this);
    474     }
    475 
    476     @Override
    477     public void actionPerformed(ActionEvent e) {
    478       if (thread != null) {
    479         thread.stopWalk();
    480       }
    481     }
    482 
    483     @Override
    484     public void walkStarted(WalkThread thread) {
    485       this.thread = thread;
    486     }
    487   }
    488 
    489   private static class PlayAction extends AbstractAction implements WalkListener {
    490 
    491     private static final long serialVersionUID = -1572747020946842769L;
    492 
    493     private transient WalkThread thread;
    494 
    495     /**
    496      * Constructs a normal PlayAction
    497      */
    498     PlayAction() {
    499       putValue(Action.NAME, I18n.tr("Play"));
    500       putValue(Action.SHORT_DESCRIPTION, I18n.tr("Continues with the paused walk."));
    501       new ImageProvider("dialogs/streetsidePlay.png").getResource().attachImageIcon(this, true);
    502       StreetsidePlugin.getStreetsideWalkAction().addListener(this);
    503     }
    504 
    505     @Override
    506     public void actionPerformed(ActionEvent e) {
    507       if (thread != null) {
    508         thread.play();
    509       }
    510     }
    511 
    512     @Override
    513     public void walkStarted(WalkThread thread) {
    514       if (thread != null) {
    515         this.thread = thread;
    516       }
    517     }
    518   }
    519 
    520   private static class PauseAction extends AbstractAction implements WalkListener {
    521 
    522     /**
    523      *
    524      */
    525     private static final long serialVersionUID = -8758326399460817222L;
    526     private WalkThread thread;
    527 
    528     /**
    529      * Constructs a normal PauseAction
    530      */
    531     PauseAction() {
    532       putValue(Action.NAME, I18n.tr("Pause"));
    533       putValue(Action.SHORT_DESCRIPTION, I18n.tr("Pauses the walk."));
    534       new ImageProvider("dialogs/streetsidePause.png").getResource().attachImageIcon(this, true);
    535       StreetsidePlugin.getStreetsideWalkAction().addListener(this);
    536     }
    537 
    538     @Override
    539     public void actionPerformed(ActionEvent e) {
    540       thread.pause();
    541     }
    542 
    543     @Override
    544     public void walkStarted(WalkThread thread) {
    545       this.thread = thread;
    546     }
    547   }
     366    public void imagesAdded() {
     367        // This method is enforced by StreetsideDataListener, but only selectedImageChanged() is needed
     368    }
     369
     370    /**
     371     * Get the image display
     372     * @return the streetsideImageDisplay
     373     */
     374    public StreetsideImageDisplay getStreetsideImageDisplay() {
     375        return streetsideImageDisplay;
     376    }
     377
     378    /**
     379     * Buttons mode.
     380     *
     381     * @author nokutu
     382     */
     383    public enum MODE {
     384        /**
     385         * Standard mode to view pictures.
     386         */
     387        NORMAL,
     388        /**
     389         * Mode when in walk.
     390         */
     391        WALK
     392    }
     393
     394    /**
     395     * Action class form the next image button.
     396     *
     397     * @author nokutu
     398     */
     399    private static class NextPictureAction extends AbstractAction {
     400
     401        @Serial
     402        private static final long serialVersionUID = 6333692154558730392L;
     403
     404        /**
     405         * Constructs a normal NextPictureAction
     406         */
     407        NextPictureAction() {
     408            super(I18n.tr("Next picture"));
     409            putValue(Action.SHORT_DESCRIPTION, I18n.tr("Shows the next picture in the sequence"));
     410            new ImageProvider("help", "next").getResource().attachImageIcon(this, true);
     411        }
     412
     413        @Override
     414        public void actionPerformed(ActionEvent e) {
     415            StreetsideLayer.getInstance().getData().selectNext();
     416        }
     417    }
     418
     419    /**
     420     * Action class for the previous image button.
     421     *
     422     * @author nokutu
     423     */
     424    private static class PreviousPictureAction extends AbstractAction {
     425
     426        @Serial
     427        private static final long serialVersionUID = 4390593660514657107L;
     428
     429        /**
     430         * Constructs a normal PreviousPictureAction
     431         */
     432        PreviousPictureAction() {
     433            super(I18n.tr("Previous picture"));
     434            putValue(Action.SHORT_DESCRIPTION, I18n.tr("Shows the previous picture in the sequence"));
     435            new ImageProvider("help", "previous").getResource().attachImageIcon(this, true);
     436        }
     437
     438        @Override
     439        public void actionPerformed(ActionEvent e) {
     440            StreetsideLayer.getInstance().getData().selectPrevious();
     441        }
     442    }
     443
     444    /**
     445     * Action class to jump to the image following the red line.
     446     *
     447     * @author nokutu
     448     */
     449    private static class RedAction extends AbstractAction {
     450
     451        @Serial
     452        private static final long serialVersionUID = -1244456062285831231L;
     453
     454        /**
     455         * Constructs a normal RedAction
     456         */
     457        RedAction() {
     458            putValue(Action.NAME, I18n.tr("Jump to red"));
     459            putValue(Action.SHORT_DESCRIPTION, I18n.tr("Jumps to the picture at the other side of the red line"));
     460            new ImageProvider("dialogs", "red").getResource().attachImageIcon(this, true);
     461        }
     462
     463        @Override
     464        public void actionPerformed(ActionEvent e) {
     465            if (StreetsideMainDialog.getInstance().getImage() != null) {
     466                StreetsideLayer.getInstance().getData()
     467                        .setSelectedImage(StreetsideLayer.getInstance().getNNearestImage(1), true);
     468            }
     469        }
     470    }
     471
     472    /**
     473     * Action class to jump to the image following the blue line.
     474     *
     475     * @author nokutu
     476     */
     477    private static class BlueAction extends AbstractAction {
     478
     479        @Serial
     480        private static final long serialVersionUID = 5951233534212838780L;
     481
     482        /**
     483         * Constructs a normal BlueAction
     484         */
     485        BlueAction() {
     486            putValue(Action.NAME, I18n.tr("Jump to blue"));
     487            putValue(Action.SHORT_DESCRIPTION, I18n.tr("Jumps to the picture at the other side of the blue line"));
     488            new ImageProvider("dialogs", "blue").getResource().attachImageIcon(this, true);
     489        }
     490
     491        @Override
     492        public void actionPerformed(ActionEvent e) {
     493            if (StreetsideMainDialog.getInstance().getImage() != null) {
     494                StreetsideLayer.getInstance().getData()
     495                        .setSelectedImage(StreetsideLayer.getInstance().getNNearestImage(2), true);
     496            }
     497        }
     498    }
     499
     500    private static class StopAction extends AbstractAction implements WalkListener {
     501
     502        @Serial
     503        private static final long serialVersionUID = 8789972456611625341L;
     504
     505        private WalkThread thread;
     506
     507        /**
     508         * Constructs a normal StopAction
     509         */
     510        StopAction() {
     511            putValue(Action.NAME, I18n.tr("Stop"));
     512            putValue(Action.SHORT_DESCRIPTION, I18n.tr("Stops the walk."));
     513            new ImageProvider("dialogs/streetsideStop.png").getResource().attachImageIcon(this, true);
     514            StreetsidePlugin.getStreetsideWalkAction().addListener(this);
     515        }
     516
     517        @Override
     518        public void actionPerformed(ActionEvent e) {
     519            if (thread != null) {
     520                thread.stopWalk();
     521            }
     522        }
     523
     524        @Override
     525        public void walkStarted(WalkThread thread) {
     526            this.thread = thread;
     527        }
     528    }
     529
     530    private static class PlayAction extends AbstractAction implements WalkListener {
     531
     532        @Serial
     533        private static final long serialVersionUID = -1572747020946842769L;
     534
     535        private transient WalkThread thread;
     536
     537        /**
     538         * Constructs a normal PlayAction
     539         */
     540        PlayAction() {
     541            putValue(Action.NAME, I18n.tr("Play"));
     542            putValue(Action.SHORT_DESCRIPTION, I18n.tr("Continues with the paused walk."));
     543            new ImageProvider("dialogs/streetsidePlay.png").getResource().attachImageIcon(this, true);
     544            StreetsidePlugin.getStreetsideWalkAction().addListener(this);
     545        }
     546
     547        @Override
     548        public void actionPerformed(ActionEvent e) {
     549            if (thread != null) {
     550                thread.play();
     551            }
     552        }
     553
     554        @Override
     555        public void walkStarted(WalkThread thread) {
     556            if (thread != null) {
     557                this.thread = thread;
     558            }
     559        }
     560    }
     561
     562    private static class PauseAction extends AbstractAction implements WalkListener {
     563
     564        /**
     565         *
     566         */
     567        @Serial
     568        private static final long serialVersionUID = -8758326399460817222L;
     569        private WalkThread thread;
     570
     571        /**
     572         * Constructs a normal PauseAction
     573         */
     574        PauseAction() {
     575            putValue(Action.NAME, I18n.tr("Pause"));
     576            putValue(Action.SHORT_DESCRIPTION, I18n.tr("Pauses the walk."));
     577            new ImageProvider("dialogs/streetsidePause.png").getResource().attachImageIcon(this, true);
     578            StreetsidePlugin.getStreetsideWalkAction().addListener(this);
     579        }
     580
     581        @Override
     582        public void actionPerformed(ActionEvent e) {
     583            thread.pause();
     584        }
     585
     586        @Override
     587        public void walkStarted(WalkThread thread) {
     588            this.thread = thread;
     589        }
     590    }
    548591}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsidePreferenceSetting.java

    r36194 r36228  
    33
    44import java.awt.BorderLayout;
    5 import java.awt.Color;
    65import java.awt.GridBagConstraints;
    76import java.awt.GridBagLayout;
    8 import java.awt.event.ActionEvent;
    97import java.io.IOException;
    108import java.io.InputStream;
     9import java.util.Objects;
    1110import java.util.logging.Logger;
    1211
    1312import javax.imageio.ImageIO;
    14 import javax.swing.AbstractAction;
    1513import javax.swing.BorderFactory;
    1614import javax.swing.Box;
    17 import javax.swing.BoxLayout;
    1815import javax.swing.ImageIcon;
    19 import javax.swing.JButton;
    2016import javax.swing.JCheckBox;
    2117import javax.swing.JComboBox;
     
    2521import javax.swing.JSpinner;
    2622import javax.swing.SpinnerNumberModel;
    27 import javax.swing.SwingUtilities;
    2823
    2924import org.openstreetmap.josm.actions.ExpertToggleAction;
     
    3227import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
    3328import org.openstreetmap.josm.plugins.streetside.StreetsidePlugin;
    34 import org.openstreetmap.josm.plugins.streetside.gui.boilerplate.StreetsideButton;
    3529import org.openstreetmap.josm.plugins.streetside.io.download.StreetsideDownloader.DOWNLOAD_MODE;
    36 import org.openstreetmap.josm.plugins.streetside.oauth.OAuthPortListener;
    37 import org.openstreetmap.josm.plugins.streetside.oauth.StreetsideLoginListener;
    38 import org.openstreetmap.josm.plugins.streetside.oauth.StreetsideUser;
    3930import org.openstreetmap.josm.plugins.streetside.utils.StreetsideColorScheme;
    4031import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
     
    4940 *
    5041 */
    51 public class StreetsidePreferenceSetting implements SubPreferenceSetting, StreetsideLoginListener {
     42public class StreetsidePreferenceSetting implements SubPreferenceSetting {
    5243
    53   private static final Logger logger = Logger.getLogger(StreetsidePreferenceSetting.class.getCanonicalName());
     44    private static final Logger logger = Logger.getLogger(StreetsidePreferenceSetting.class.getCanonicalName());
    5445
    55   private final JComboBox<String> downloadModeComboBox = new JComboBox<>(
    56       new String[] { DOWNLOAD_MODE.VISIBLE_AREA.getLabel(), DOWNLOAD_MODE.OSM_AREA.getLabel(),
    57           DOWNLOAD_MODE.MANUAL_ONLY.getLabel() });
     46    private final JComboBox<String> downloadModeComboBox = new JComboBox<>(
     47            new String[] { DOWNLOAD_MODE.VISIBLE_AREA.getLabel(), DOWNLOAD_MODE.OSM_AREA.getLabel(),
     48                    DOWNLOAD_MODE.MANUAL_ONLY.getLabel() });
    5849
    59   private final JCheckBox displayHour = new JCheckBox(I18n.tr("Display hour when the picture was taken"),
    60       StreetsideProperties.DISPLAY_HOUR.get());
    61   private final JCheckBox format24 = new JCheckBox(I18n.tr("Use 24 hour format"),
    62       StreetsideProperties.TIME_FORMAT_24.get());
    63   private final JCheckBox moveTo = new JCheckBox(I18n.tr("Move to picture''s location with next/previous buttons"),
    64       StreetsideProperties.MOVE_TO_IMG.get());
    65   private final JCheckBox hoverEnabled = new JCheckBox(I18n.tr("Preview images when hovering its icon"),
    66       StreetsideProperties.HOVER_ENABLED.get());
    67   private final JCheckBox cutOffSeq = new JCheckBox(I18n.tr("Cut off sequences at download bounds"),
    68       StreetsideProperties.CUT_OFF_SEQUENCES_AT_BOUNDS.get());
    69   private final JCheckBox imageLinkToBlurEditor = new JCheckBox(
    70       I18n.tr("When opening Streetside image in web browser, show the blur editor instead of the image viewer"),
    71       StreetsideProperties.IMAGE_LINK_TO_BLUR_EDITOR.get());
    72   private final JCheckBox developer = new JCheckBox(I18n.tr("Enable experimental beta-features (might be unstable)"),
    73       StreetsideProperties.DEVELOPER.get());
    74   private final SpinnerNumberModel preFetchSize = new SpinnerNumberModel(
    75       StreetsideProperties.PRE_FETCH_IMAGE_COUNT.get().intValue(), 0, Integer.MAX_VALUE, 1);
    76   private final JButton loginButton = new StreetsideButton(new LoginAction(this));
    77   private final JButton logoutButton = new StreetsideButton(new LogoutAction());
    78   private final JLabel loginLabel = new JLabel();
    79   private final JPanel loginPanel = new JPanel();
     50    private final JCheckBox displayHour = new JCheckBox(I18n.tr("Display hour when the picture was taken"),
     51            StreetsideProperties.DISPLAY_HOUR.get());
     52    private final JCheckBox format24 = new JCheckBox(I18n.tr("Use 24 hour format"),
     53            StreetsideProperties.TIME_FORMAT_24.get());
     54    private final JCheckBox moveTo = new JCheckBox(I18n.tr("Move to picture''s location with next/previous buttons"),
     55            StreetsideProperties.MOVE_TO_IMG.get());
     56    private final JCheckBox hoverEnabled = new JCheckBox(I18n.tr("Preview images when hovering its icon"),
     57            StreetsideProperties.HOVER_ENABLED.get());
     58    private final JCheckBox cutOffSeq = new JCheckBox(I18n.tr("Cut off sequences at download bounds"),
     59            StreetsideProperties.CUT_OFF_SEQUENCES_AT_BOUNDS.get());
     60    private final JCheckBox imageLinkToBlurEditor = new JCheckBox(
     61            I18n.tr("When opening Streetside image in web browser, show the blur editor instead of the image viewer"),
     62            StreetsideProperties.IMAGE_LINK_TO_BLUR_EDITOR.get());
     63    private final JCheckBox developer = new JCheckBox(I18n.tr("Enable experimental beta-features (might be unstable)"),
     64            StreetsideProperties.DEVELOPER.get());
     65    private final SpinnerNumberModel preFetchSize = new SpinnerNumberModel(
     66            StreetsideProperties.PRE_FETCH_IMAGE_COUNT.get().intValue(), 0, Integer.MAX_VALUE, 1);
    8067
    81   @Override
    82   public TabPreferenceSetting getTabPreferenceSetting(PreferenceTabbedPane gui) {
    83     return gui.getDisplayPreference();
    84   }
    85 
    86   @Override
    87   public void addGui(PreferenceTabbedPane gui) {
    88     JPanel container = new JPanel(new BorderLayout());
    89 
    90     loginPanel.setLayout(new BoxLayout(loginPanel, BoxLayout.LINE_AXIS));
    91     loginPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
    92     loginPanel.setBackground(StreetsideColorScheme.TOOLBAR_DARK_GREY);
    93     JLabel brandImage = new JLabel();
    94     try (InputStream is = StreetsidePreferenceSetting.class
    95         .getResourceAsStream("/images/streetside-logo-white.png")) {
    96       if (is != null) {
    97         brandImage.setIcon(new ImageIcon(ImageIO.read(is)));
    98       } else {
    99         logger.log(Logging.LEVEL_WARN, "Could not load Streetside brand image!");
    100       }
    101     } catch (IOException e) {
    102       logger.log(Logging.LEVEL_WARN, "While reading Streetside brand image, an IO-exception occured!", e);
    103     }
    104     loginPanel.add(brandImage, 0);
    105     loginPanel.add(Box.createHorizontalGlue(), 1);
    106     loginLabel.setForeground(Color.WHITE);
    107     loginLabel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
    108     loginPanel.add(loginLabel, 2);
    109     loginPanel.add(loginButton, 3);
    110     onLogout();
    111     container.add(loginPanel, BorderLayout.NORTH);
    112 
    113     JPanel mainPanel = new JPanel();
    114     mainPanel.setLayout(new GridBagLayout());
    115     mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    116 
    117     downloadModeComboBox
    118         .setSelectedItem(DOWNLOAD_MODE.fromPrefId(StreetsideProperties.DOWNLOAD_MODE.get()).getLabel());
    119 
    120     JPanel downloadModePanel = new JPanel();
    121     downloadModePanel.add(new JLabel(I18n.tr("Download mode")));
    122     downloadModePanel.add(downloadModeComboBox);
    123     mainPanel.add(downloadModePanel, GBC.eol());
    124 
    125     mainPanel.add(displayHour, GBC.eol());
    126     mainPanel.add(format24, GBC.eol());
    127     mainPanel.add(moveTo, GBC.eol());
    128     mainPanel.add(hoverEnabled, GBC.eol());
    129     mainPanel.add(cutOffSeq, GBC.eol());
    130     mainPanel.add(imageLinkToBlurEditor, GBC.eol());
    131 
    132     final JPanel preFetchPanel = new JPanel();
    133     preFetchPanel.add(new JLabel(I18n.tr("Number of images to be pre-fetched (forwards and backwards)")));
    134     final JSpinner spinner = new JSpinner(preFetchSize);
    135     final JSpinner.DefaultEditor editor = new JSpinner.NumberEditor(spinner);
    136     editor.getTextField().setColumns(3);
    137     spinner.setEditor(editor);
    138     preFetchPanel.add(spinner);
    139     mainPanel.add(preFetchPanel, GBC.eol());
    140 
    141     if (ExpertToggleAction.isExpert() || developer.isSelected()) {
    142       mainPanel.add(developer, GBC.eol());
    143     }
    144     StreetsideColorScheme.styleAsDefaultPanel(mainPanel, downloadModePanel, displayHour, format24, moveTo,
    145         hoverEnabled, cutOffSeq, imageLinkToBlurEditor, developer, preFetchPanel);
    146     mainPanel.add(Box.createVerticalGlue(), GBC.eol().fill(GridBagConstraints.BOTH));
    147 
    148     container.add(mainPanel, BorderLayout.CENTER);
    149 
    150     synchronized (gui.getDisplayPreference().getTabPane()) {
    151       gui.getDisplayPreference().addSubTab(this, "Streetside", new JScrollPane(container));
    152       gui.getDisplayPreference().getTabPane().setIconAt(gui.getDisplayPreference().getTabPane().getTabCount() - 1,
    153           StreetsidePlugin.LOGO.setSize(12, 12).get());
    154     }
    155 
    156     new Thread(() -> {
    157       String username = StreetsideUser.getUsername();
    158       if (username != null) {
    159         SwingUtilities.invokeLater(() -> onLogin(StreetsideUser.getUsername()));
    160       }
    161     }).start();
    162   }
    163 
    164   @Override
    165   public void onLogin(final String username) {
    166     loginPanel.remove(loginButton);
    167     loginPanel.add(logoutButton, 3);
    168     loginLabel.setText(I18n.tr("You are logged in as ''{0}''.", username));
    169     loginPanel.revalidate();
    170     loginPanel.repaint();
    171   }
    172 
    173   @Override
    174   public void onLogout() {
    175     loginPanel.remove(logoutButton);
    176     loginPanel.add(loginButton, 3);
    177     loginLabel.setText(I18n.tr("You are currently not logged in."));
    178     loginPanel.revalidate();
    179     loginPanel.repaint();
    180   }
    181 
    182   @SuppressWarnings("PMD.ShortMethodName")
    183   @Override
    184   public boolean ok() {
    185     StreetsideProperties.DOWNLOAD_MODE
    186         .put(DOWNLOAD_MODE.fromLabel(downloadModeComboBox.getSelectedItem().toString()).getPrefId());
    187     StreetsideProperties.DISPLAY_HOUR.put(displayHour.isSelected());
    188     StreetsideProperties.TIME_FORMAT_24.put(format24.isSelected());
    189     StreetsideProperties.MOVE_TO_IMG.put(moveTo.isSelected());
    190     StreetsideProperties.HOVER_ENABLED.put(hoverEnabled.isSelected());
    191     StreetsideProperties.CUT_OFF_SEQUENCES_AT_BOUNDS.put(cutOffSeq.isSelected());
    192     StreetsideProperties.IMAGE_LINK_TO_BLUR_EDITOR.put(imageLinkToBlurEditor.isSelected());
    193     StreetsideProperties.DEVELOPER.put(developer.isSelected());
    194     StreetsideProperties.PRE_FETCH_IMAGE_COUNT.put(preFetchSize.getNumber().intValue());
    195 
    196     //Restart is never required
    197     return false;
    198   }
    199 
    200   @Override
    201   public boolean isExpert() {
    202     return false;
    203   }
    204 
    205   /**
    206    * Opens the StreetsideOAuthUI window and lets the user log in.
    207    *
    208    * @author nokutu
    209    */
    210   private static class LoginAction extends AbstractAction {
    211 
    212     private static final long serialVersionUID = 8743119160917296506L;
    213 
    214     private final transient StreetsideLoginListener callback;
    215 
    216     LoginAction(StreetsideLoginListener loginCallback) {
    217       super(I18n.tr("Login"));
    218       callback = loginCallback;
     68    @Override
     69    public TabPreferenceSetting getTabPreferenceSetting(PreferenceTabbedPane gui) {
     70        return gui.getDisplayPreference();
    21971    }
    22072
    22173    @Override
    222     public void actionPerformed(ActionEvent arg0) {
    223       OAuthPortListener portListener = new OAuthPortListener(callback);
    224       portListener.start();
    225       // user authentication not supported for Streetside (Mapillary relic)
     74    public void addGui(PreferenceTabbedPane gui) {
     75        JPanel container = new JPanel(new BorderLayout());
     76
     77        JLabel brandImage = new JLabel();
     78        try (InputStream is = StreetsidePreferenceSetting.class
     79                .getResourceAsStream("/images/streetside-logo-white.png")) {
     80            if (is != null) {
     81                brandImage.setIcon(new ImageIcon(ImageIO.read(is)));
     82            } else {
     83                logger.log(Logging.LEVEL_WARN, "Could not load Streetside brand image!");
     84            }
     85        } catch (IOException e) {
     86            logger.log(Logging.LEVEL_WARN, "While reading Streetside brand image, an IO-exception occured!", e);
     87        }
     88
     89        JPanel mainPanel = new JPanel();
     90        mainPanel.setLayout(new GridBagLayout());
     91        mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
     92
     93        downloadModeComboBox
     94                .setSelectedItem(DOWNLOAD_MODE.fromPrefId(StreetsideProperties.DOWNLOAD_MODE.get()).getLabel());
     95
     96        JPanel downloadModePanel = new JPanel();
     97        downloadModePanel.add(new JLabel(I18n.tr("Download mode")));
     98        downloadModePanel.add(downloadModeComboBox);
     99        mainPanel.add(downloadModePanel, GBC.eol());
     100
     101        mainPanel.add(displayHour, GBC.eol());
     102        mainPanel.add(format24, GBC.eol());
     103        mainPanel.add(moveTo, GBC.eol());
     104        mainPanel.add(hoverEnabled, GBC.eol());
     105        mainPanel.add(cutOffSeq, GBC.eol());
     106        mainPanel.add(imageLinkToBlurEditor, GBC.eol());
     107
     108        final JPanel preFetchPanel = new JPanel();
     109        preFetchPanel.add(new JLabel(I18n.tr("Number of images to be pre-fetched (forwards and backwards)")));
     110        final JSpinner spinner = new JSpinner(preFetchSize);
     111        final JSpinner.DefaultEditor editor = new JSpinner.NumberEditor(spinner);
     112        editor.getTextField().setColumns(3);
     113        spinner.setEditor(editor);
     114        preFetchPanel.add(spinner);
     115        mainPanel.add(preFetchPanel, GBC.eol());
     116
     117        if (ExpertToggleAction.isExpert() || developer.isSelected()) {
     118            mainPanel.add(developer, GBC.eol());
     119        }
     120        StreetsideColorScheme.styleAsDefaultPanel(mainPanel, downloadModePanel, displayHour, format24, moveTo,
     121                hoverEnabled, cutOffSeq, imageLinkToBlurEditor, developer, preFetchPanel);
     122        mainPanel.add(Box.createVerticalGlue(), GBC.eol().fill(GridBagConstraints.BOTH));
     123
     124        container.add(mainPanel, BorderLayout.CENTER);
     125
     126        synchronized (gui.getDisplayPreference().getTabPane()) {
     127            gui.getDisplayPreference().addSubTab(this, "Streetside", new JScrollPane(container));
     128            gui.getDisplayPreference().getTabPane().setIconAt(gui.getDisplayPreference().getTabPane().getTabCount() - 1,
     129                    StreetsidePlugin.LOGO.setSize(12, 12).get());
     130        }
    226131    }
    227   }
    228132
    229   /**
    230    * Logs the user out.
    231    *
    232    * @author nokutu
    233    *
    234    */
    235   private class LogoutAction extends AbstractAction {
     133    @SuppressWarnings("PMD.ShortMethodName")
     134    @Override
     135    public boolean ok() {
     136        StreetsideProperties.DOWNLOAD_MODE
     137                .put(DOWNLOAD_MODE.fromLabel(Objects.requireNonNull(downloadModeComboBox.getSelectedItem()).toString()).getPrefId());
     138        StreetsideProperties.DISPLAY_HOUR.put(displayHour.isSelected());
     139        StreetsideProperties.TIME_FORMAT_24.put(format24.isSelected());
     140        StreetsideProperties.MOVE_TO_IMG.put(moveTo.isSelected());
     141        StreetsideProperties.HOVER_ENABLED.put(hoverEnabled.isSelected());
     142        StreetsideProperties.CUT_OFF_SEQUENCES_AT_BOUNDS.put(cutOffSeq.isSelected());
     143        StreetsideProperties.IMAGE_LINK_TO_BLUR_EDITOR.put(imageLinkToBlurEditor.isSelected());
     144        StreetsideProperties.DEVELOPER.put(developer.isSelected());
     145        StreetsideProperties.PRE_FETCH_IMAGE_COUNT.put(preFetchSize.getNumber().intValue());
    236146
    237     private static final long serialVersionUID = -4146587895393766981L;
    238 
    239     private LogoutAction() {
    240       super(I18n.tr("Logout"));
     147        //Restart is never required
     148        return false;
    241149    }
    242150
    243151    @Override
    244     public void actionPerformed(ActionEvent arg0) {
    245       StreetsideUser.reset();
    246       onLogout();
     152    public boolean isExpert() {
     153        return false;
    247154    }
    248   }
    249155}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsideViewerDialog.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside.gui;
    33
    4 import java.awt.BorderLayout;
    5 import java.awt.Component;
    6 import java.util.List;
     4import java.io.Serial;
    75
    8 import org.openstreetmap.josm.gui.SideButton;
    96import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
    107import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.StreetsideViewerPanel;
     
    1815public final class StreetsideViewerDialog extends ToggleDialog {
    1916
    20   private static final long serialVersionUID = -8983900297628236197L;
     17    @Serial
     18    private static final long serialVersionUID = 6424974077669812562L;
    2119
    22   private static final String BASE_TITLE = "360° Streetside Viewer";
     20    private static final String BASE_TITLE = "360° Streetside Viewer";
    2321
    24   private static StreetsideViewerDialog instance;
     22    private static StreetsideViewerDialog instance;
    2523
    26   /**
    27    * Object containing the shown image and that handles zoom and drag
    28    */
    29   private final StreetsideViewerPanel streetsideViewerPanel;
     24    /**
     25     * Object containing the shown image and that handles zoom and drag
     26     */
     27    private final StreetsideViewerPanel streetsideViewerPanel;
    3028
    31   private StreetsideViewerDialog() {
    32     super(StreetsideViewerDialog.BASE_TITLE, "streetside-viewer", "Open Streetside Viewer window", null, 200, true,
    33         StreetsidePreferenceSetting.class);
    34     streetsideViewerPanel = new StreetsideViewerPanel();
    35     createLayout(streetsideViewerPanel, true, null);
    36   }
     29    private StreetsideViewerDialog() {
     30        super(StreetsideViewerDialog.BASE_TITLE, "streetside-viewer", "Open Streetside Viewer window", null, 200, true,
     31                StreetsidePreferenceSetting.class);
     32        streetsideViewerPanel = new StreetsideViewerPanel();
     33        createLayout(streetsideViewerPanel, true, null);
     34    }
    3735
    38   /**
    39    * Returns the unique instance of the class.
    40    *
    41    * @return The unique instance of the class.
    42    */
    43   public static synchronized StreetsideViewerDialog getInstance() {
    44     if (StreetsideViewerDialog.instance == null) {
    45       StreetsideViewerDialog.instance = new StreetsideViewerDialog();
     36    /**
     37     * Returns the unique instance of the class.
     38     *
     39     * @return The unique instance of the class.
     40     */
     41    public static synchronized StreetsideViewerDialog getInstance() {
     42        if (StreetsideViewerDialog.instance == null) {
     43            StreetsideViewerDialog.instance = new StreetsideViewerDialog();
     44        }
     45        return StreetsideViewerDialog.instance;
    4646    }
    47     return StreetsideViewerDialog.instance;
    48   }
    4947
    50   /**
    51    * @return true, iff the singleton instance is present
    52    */
    53   public static boolean hasInstance() {
    54     return StreetsideViewerDialog.instance != null;
    55   }
     48    /**
     49     * Destroys the unique instance of the class.
     50     */
     51    public static synchronized void destroyInstance() {
     52        StreetsideViewerDialog.instance = null;
     53    }
    5654
    57   /**
    58    * Destroys the unique instance of the class.
    59    */
    60   public static synchronized void destroyInstance() {
    61     StreetsideViewerDialog.instance = null;
    62   }
    63 
    64   /**
    65    * Creates the layout of the dialog.
    66    *
    67    * @param data  The content of the dialog
    68    * @param buttons The buttons where you can click
    69    */
    70   public void createLayout(Component data, List<SideButton> buttons) {
    71     removeAll();
    72     createLayout(data, true, buttons);
    73     add(titleBar, BorderLayout.NORTH);
    74   }
    75 
    76   public StreetsideViewerPanel getStreetsideViewerPanel() {
    77     return streetsideViewerPanel;
    78   }
     55    public StreetsideViewerPanel getStreetsideViewerPanel() {
     56        return streetsideViewerPanel;
     57    }
    7958}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsideWalkDialog.java

    r36194 r36228  
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.io.Serial;
    57
    68import javax.swing.JCheckBox;
     
    1921public class StreetsideWalkDialog extends JPanel {
    2022
    21   private static final long serialVersionUID = 7974881240732957573L;
     23    @Serial
     24    private static final long serialVersionUID = 7974881240732957573L;
    2225
    23   /**
    24    * Spin containing the interval value.
    25    */
    26   public SpinnerModel spin;
    27   /**
    28    * Whether it must wait for the picture to be downloaded
    29    */
    30   public JCheckBox waitForPicture;
    31   /**
    32    * Whether the view must follow the selected image.
    33    */
    34   public JCheckBox followSelection;
    35   /**
    36    * Go forward or backwards
    37    */
    38   public JCheckBox goForward;
     26    /**
     27     * Spin containing the interval value.
     28     */
     29    public final SpinnerModel spin;
     30    /**
     31     * Whether it must wait for the picture to be downloaded
     32     */
     33    public final JCheckBox waitForPicture;
     34    /**
     35     * Whether the view must follow the selected image.
     36     */
     37    public final JCheckBox followSelection;
     38    /**
     39     * Go forward or backwards
     40     */
     41    public final JCheckBox goForward;
    3942
    40   /**
    41    * Main constructor
    42    */
    43   public StreetsideWalkDialog() {
    44     JPanel interval = new JPanel();
    45     spin = new SpinnerNumberModel(2000, 500, 10000, 500);
    46     interval.add(new JLabel("Interval (miliseconds): "));
    47     interval.add(new JSpinner(spin));
    48     add(interval);
     43    /**
     44     * Main constructor
     45     */
     46    public StreetsideWalkDialog() {
     47        JPanel interval = new JPanel();
     48        spin = new SpinnerNumberModel(2000, 500, 10000, 500);
     49        interval.add(new JLabel("Interval (miliseconds): "));
     50        interval.add(new JSpinner(spin));
     51        add(interval);
    4952
    50     waitForPicture = new JCheckBox(tr("Wait for full quality pictures"));
    51     waitForPicture.setSelected(true);
    52     add(waitForPicture);
     53        waitForPicture = new JCheckBox(tr("Wait for full quality pictures"));
     54        waitForPicture.setSelected(true);
     55        add(waitForPicture);
    5356
    54     followSelection = new JCheckBox(tr("Follow selected image"));
    55     followSelection.setSelected(true);
    56     add(followSelection);
     57        followSelection = new JCheckBox(tr("Follow selected image"));
     58        followSelection.setSelected(true);
     59        add(followSelection);
    5760
    58     goForward = new JCheckBox(tr("Go forward"));
    59     goForward.setSelected(true);
    60     add(goForward);
    61   }
     61        goForward = new JCheckBox(tr("Go forward"));
     62        goForward.setSelected(true);
     63        add(goForward);
     64    }
    6265}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/boilerplate/SelectableLabel.java

    r36194 r36228  
    44import java.awt.Color;
    55import java.awt.Font;
     6import java.io.Serial;
    67
    78import javax.swing.JTextPane;
     
    1011public class SelectableLabel extends JTextPane {
    1112
    12   public static final Font DEFAULT_FONT = UIManager.getFont("Label.font").deriveFont(Font.PLAIN);
    13   public static final Color DEFAULT_BACKGROUND = UIManager.getColor("Panel.background");
    14   private static final long serialVersionUID = 5432480892000739831L;
     13    public static final Font DEFAULT_FONT = UIManager.getFont("Label.font").deriveFont(Font.PLAIN);
     14    public static final Color DEFAULT_BACKGROUND = UIManager.getColor("Panel.background");
     15    @Serial
     16    private static final long serialVersionUID = 5432480892000739831L;
    1517
    16   public SelectableLabel() {
    17     super();
    18     init();
    19   }
     18    public SelectableLabel() {
     19        super();
     20        init();
     21    }
    2022
    21   public SelectableLabel(String text) {
    22     this();
    23     setText(text);
    24   }
     23    public SelectableLabel(String text) {
     24        this();
     25        setText(text);
     26    }
    2527
    26   private void init() {
    27     setEditable(false);
    28     setFont(DEFAULT_FONT);
    29     setContentType("text/html");
    30     setBackground(DEFAULT_BACKGROUND);
    31     setBorder(null);
    32   }
     28    private void init() {
     29        setEditable(false);
     30        setFont(DEFAULT_FONT);
     31        setContentType("text/html");
     32        setBackground(DEFAULT_BACKGROUND);
     33        setBorder(null);
     34    }
    3335}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/boilerplate/StreetsideButton.java

    r36194 r36228  
    66import java.awt.Graphics2D;
    77import java.awt.RenderingHints;
     8import java.io.Serial;
    89
    910import javax.swing.Action;
     
    1516public class StreetsideButton extends JButton {
    1617
    17   private static final long serialVersionUID = -3060978712233067368L;
     18    @Serial
     19    private static final long serialVersionUID = -3060978712233067368L;
    1820
    19   public StreetsideButton(final Action action) {
    20     this(action, false);
    21   }
     21    public StreetsideButton(final Action action) {
     22        this(action, false);
     23    }
    2224
    23   public StreetsideButton(final Action action, boolean slim) {
    24     super(action);
    25     setForeground(Color.WHITE);
    26     setBorder(slim ? BorderFactory.createEmptyBorder(3, 4, 3, 4) : BorderFactory.createEmptyBorder(7, 10, 7, 10));
    27   }
     25    public StreetsideButton(final Action action, boolean slim) {
     26        super(action);
     27        setForeground(Color.WHITE);
     28        setBorder(slim ? BorderFactory.createEmptyBorder(3, 4, 3, 4) : BorderFactory.createEmptyBorder(7, 10, 7, 10));
     29    }
    2830
    29   @Override
    30   protected void paintComponent(final Graphics g) {
    31     if (!isEnabled()) {
    32       g.setColor(StreetsideColorScheme.TOOLBAR_DARK_GREY);
    33     } else if (getModel().isPressed()) {
    34       g.setColor(StreetsideColorScheme.STREETSIDE_BLUE.darker().darker());
    35     } else if (getModel().isRollover()) {
    36       g.setColor(StreetsideColorScheme.STREETSIDE_BLUE.darker());
    37     } else {
    38       g.setColor(StreetsideColorScheme.STREETSIDE_BLUE);
     31    @Override
     32    protected void paintComponent(final Graphics g) {
     33        if (!isEnabled()) {
     34            g.setColor(StreetsideColorScheme.TOOLBAR_DARK_GREY);
     35        } else if (getModel().isPressed()) {
     36            g.setColor(StreetsideColorScheme.STREETSIDE_BLUE.darker().darker());
     37        } else if (getModel().isRollover()) {
     38            g.setColor(StreetsideColorScheme.STREETSIDE_BLUE.darker());
     39        } else {
     40            g.setColor(StreetsideColorScheme.STREETSIDE_BLUE);
     41        }
     42        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
     43        g.fillRoundRect(0, 0, getWidth(), getHeight(), 3, 3);
     44        super.paintComponent(g);
    3945    }
    40     ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    41     g.fillRoundRect(0, 0, getWidth(), getHeight(), 3, 3);
    42     super.paintComponent(g);
    43   }
    4446
    45   @Override
    46   public boolean isContentAreaFilled() {
    47     return false;
    48   }
     47    @Override
     48    public boolean isContentAreaFilled() {
     49        return false;
     50    }
    4951}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ImageInfoHelpPopup.java

    r36194 r36228  
    77import java.awt.IllegalComponentStateException;
    88import java.awt.event.ActionEvent;
     9import java.io.Serial;
    910import java.util.logging.Logger;
    1011
     
    2526public class ImageInfoHelpPopup extends JPopupMenu {
    2627
    27   private static final long serialVersionUID = -1721594904273820586L;
     28    @Serial
     29    private static final long serialVersionUID = -1721594904273820586L;
    2830
    29   private static final Logger LOGGER = Logger.getLogger(ImageInfoHelpPopup.class.getCanonicalName());
     31    private static final Logger LOGGER = Logger.getLogger(ImageInfoHelpPopup.class.getCanonicalName());
    3032
    31   private final Component invokerComp;
    32   private boolean alreadyDisplayed;
     33    private final Component invokerComp;
     34    private boolean alreadyDisplayed;
    3335
    34   public ImageInfoHelpPopup(Component invoker) {
    35     invokerComp = invoker;
    36     removeAll();
    37     setLayout(new BorderLayout());
     36    public ImageInfoHelpPopup(Component invoker) {
     37        invokerComp = invoker;
     38        removeAll();
     39        setLayout(new BorderLayout());
    3840
    39     JPanel topBar = new JPanel();
    40     topBar.add(new JLabel(ImageProvider.get("streetside-logo-white")));
    41     topBar.setBackground(StreetsideColorScheme.TOOLBAR_DARK_GREY);
    42     add(topBar, BorderLayout.NORTH);
     41        JPanel topBar = new JPanel();
     42        topBar.add(new JLabel(ImageProvider.get("streetside-logo-white")));
     43        topBar.setBackground(StreetsideColorScheme.TOOLBAR_DARK_GREY);
     44        add(topBar, BorderLayout.NORTH);
    4345
    44     JTextPane mainText = new JTextPane();
    45     mainText.setContentType("text/html");
    46     mainText.setFont(SelectableLabel.DEFAULT_FONT);
    47     mainText.setText("<html><div style='width:250px'>"
    48         + "Welcome to the Microsoft Streetside JOSM Plugin. To view the vector bubbles for the 360 degree imagery, select Imagery->Streetside from the JOSM menu."
    49         + "<br><br>"
    50         + "Once the blue bubbles appear on the map, click on a vector bubble and undock/maximize the 360 viewer to view the imagery."
    51         + "</div></html>");
    52     add(mainText, BorderLayout.CENTER);
     46        JTextPane mainText = new JTextPane();
     47        mainText.setContentType("text/html");
     48        mainText.setFont(SelectableLabel.DEFAULT_FONT);
     49        mainText.setText("<html><div style='width:250px'>"
     50                + "Welcome to the Microsoft Streetside JOSM Plugin. To view the vector bubbles for the 360 degree imagery, select Imagery->Streetside from the JOSM menu."
     51                + "<br><br>"
     52                + "Once the blue bubbles appear on the map, click on a vector bubble and undock/maximize the 360 viewer to view the imagery."
     53                + "</div></html>");
     54        add(mainText, BorderLayout.CENTER);
    5355
    54     JPanel bottomBar = new JPanel();
    55     bottomBar.setBackground(new Color(0x00FFFFFF, true));
    56     StreetsideButton infoButton = new StreetsideButton(ImageInfoPanel.getInstance().getToggleAction());
    57     infoButton.addActionListener(e -> setVisible(false));
    58     bottomBar.add(infoButton);
    59     StreetsideButton closeBtn = new StreetsideButton(new AbstractAction() {
     56        JPanel bottomBar = new JPanel();
     57        bottomBar.setBackground(new Color(0x00FFFFFF, true));
     58        StreetsideButton infoButton = new StreetsideButton(ImageInfoPanel.getInstance().getToggleAction());
     59        infoButton.addActionListener(e -> setVisible(false));
     60        bottomBar.add(infoButton);
     61        StreetsideButton closeBtn = new StreetsideButton(new AbstractAction() {
    6062
    61       private static final long serialVersionUID = 2853315308169651854L;
     63            @Serial
     64            private static final long serialVersionUID = 2853315308169651854L;
    6265
    63       @Override
    64       public void actionPerformed(ActionEvent e) {
    65         setVisible(false);
    66         StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN.put(0);
    67       }
    68     });
     66            @Override
     67            public void actionPerformed(ActionEvent e) {
     68                setVisible(false);
     69                StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN.put(0);
     70            }
     71        });
    6972
    70     closeBtn.setText(I18n.tr("I got it, close this."));
    71     bottomBar.add(closeBtn);
    72     add(bottomBar, BorderLayout.SOUTH);
     73        closeBtn.setText(I18n.tr("I got it, close this."));
     74        bottomBar.add(closeBtn);
     75        add(bottomBar, BorderLayout.SOUTH);
    7376
    74     setBackground(Color.WHITE);
    75   }
     77        setBackground(Color.WHITE);
     78    }
    7679
    77   /**
    78    * @return <code>true</code> if the popup is displayed
    79    */
    80   public boolean showPopup() {
    81     if (!alreadyDisplayed && invokerComp.isShowing()) {
    82       try {
    83         show(invokerComp, invokerComp.getWidth(), 0);
    84         alreadyDisplayed = true;
    85         return true;
    86       } catch (IllegalComponentStateException e) {
    87         LOGGER.log(Logging.LEVEL_WARN, I18n.tr(
    88             "Could not show ImageInfoHelpPopup, because probably the invoker component has disappeared from screen.",
    89             e));
    90       }
     80    /**
     81     * Show the popup
     82     * @return <code>true</code> if the popup is displayed
     83     */
     84    public boolean showPopup() {
     85        if (!alreadyDisplayed && invokerComp.isShowing()) {
     86            try {
     87                show(invokerComp, invokerComp.getWidth(), 0);
     88                alreadyDisplayed = true;
     89                return true;
     90            } catch (IllegalComponentStateException e) {
     91                LOGGER.log(Logging.LEVEL_WARN, I18n.tr(
     92                        "Could not show ImageInfoHelpPopup, because probably the invoker component has disappeared from screen.",
     93                        e));
     94            }
     95        }
     96        return false;
    9197    }
    92     return false;
    93   }
    9498}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ImageInfoPanel.java

    r36194 r36228  
    55import java.awt.GridBagLayout;
    66import java.awt.Insets;
    7 import java.awt.datatransfer.StringSelection;
     7import java.io.Serial;
    88import java.util.Collection;
    99import java.util.logging.Logger;
     
    1111import javax.swing.JLabel;
    1212import javax.swing.JPanel;
    13 import javax.swing.JTextPane;
    1413
    1514import org.openstreetmap.josm.data.osm.DataSelectionListener;
    1615import org.openstreetmap.josm.data.osm.OsmPrimitive;
    17 import org.openstreetmap.josm.data.osm.Tag;
    1816import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
    1917import org.openstreetmap.josm.data.preferences.AbstractProperty.ValueChangeListener;
    2018import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
    21 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    2219import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener;
    2320import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
    24 import org.openstreetmap.josm.plugins.streetside.gui.boilerplate.SelectableLabel;
    2521import org.openstreetmap.josm.plugins.streetside.gui.boilerplate.StreetsideButton;
    2622import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
     
    2925import org.openstreetmap.josm.tools.Logging;
    3026
     27/**
     28 * A panel for showing image information
     29 */
    3130public final class ImageInfoPanel extends ToggleDialog implements StreetsideDataListener, DataSelectionListener {
    32   private static final long serialVersionUID = 4141847503072417190L;
     31    @Serial
     32    private static final long serialVersionUID = 1898445061036887054L;
    3333
    34   private static final Logger LOGGER = Logger.getLogger(ImageInfoPanel.class.getCanonicalName());
     34    private static final Logger LOGGER = Logger.getLogger(ImageInfoPanel.class.getCanonicalName());
    3535
    36   private static ImageInfoPanel instance;
     36    private static ImageInfoPanel instance;
    3737
    38   private final JTextPane imgKeyValue;
    39   private final WebLinkAction imgLinkAction;
    40   private final ClipboardAction copyImgKeyAction;
    41   private final AddTagToPrimitiveAction addStreetsideTagAction;
    42   private final JTextPane seqKeyValue;
     38    private final WebLinkAction imgLinkAction;
    4339
    44   private ValueChangeListener<Boolean> imageLinkChangeListener;
     40    private ValueChangeListener<Boolean> imageLinkChangeListener;
    4541
    46   private ImageInfoPanel() {
    47     super(I18n.tr("Streetside 360° image info"), "streetside-info",
    48         I18n.tr("Displays detail information on the currently selected Streetside image"), null, 150);
    49     SelectionEventManager.getInstance().addSelectionListener(this);
     42    private ImageInfoPanel() {
     43        super(I18n.tr("Streetside 360° image info"), "streetside-info",
     44                I18n.tr("Displays detail information on the currently selected Streetside image"), null, 150);
     45        SelectionEventManager.getInstance().addSelectionListener(this);
    5046
    51     imgKeyValue = new SelectableLabel();
     47        imgLinkAction = new WebLinkAction(I18n.tr("View in browser"), null);
    5248
    53     imgLinkAction = new WebLinkAction(I18n.tr("View in browser"), null);
     49        final var root = new JPanel(new GridBagLayout());
    5450
    55     copyImgKeyAction = new ClipboardAction(I18n.tr("Copy key"), null);
    56     StreetsideButton copyButton = new StreetsideButton(copyImgKeyAction, true);
    57     copyImgKeyAction.setPopupParent(copyButton);
     51        final var gbc = new GridBagConstraints();
     52        gbc.insets = new Insets(0, 5, 0, 5);
    5853
    59     addStreetsideTagAction = new AddTagToPrimitiveAction(I18n.tr("Add Streetside tag"));
     54        gbc.fill = GridBagConstraints.HORIZONTAL;
     55        gbc.gridx = 0;
     56        gbc.gridy = 0;
     57        root.add(new JLabel(I18n.tr("Image actions")), gbc);
    6058
    61     seqKeyValue = new SelectableLabel();
     59        gbc.fill = GridBagConstraints.HORIZONTAL;
     60        gbc.gridx = 1;
     61        gbc.gridy = 0;
     62        root.add(new StreetsideButton(imgLinkAction, true), gbc);
    6263
    63     JPanel root = new JPanel(new GridBagLayout());
     64        gbc.fill = GridBagConstraints.HORIZONTAL;
     65        gbc.weightx = 0.5;
     66        gbc.gridx = 2;
     67        gbc.gridy = 1;
    6468
    65     GridBagConstraints gbc = new GridBagConstraints();
    66     gbc.insets = new Insets(0, 5, 0, 5);
    67 
    68     gbc.fill = GridBagConstraints.HORIZONTAL;
    69     gbc.gridx = 0;
    70     gbc.gridy = 0;
    71     root.add(new JLabel(I18n.tr("Image actions")), gbc);
    72 
    73     gbc.fill = GridBagConstraints.HORIZONTAL;
    74     gbc.weightx = 0.5;
    75     gbc.gridx = 0;
    76     gbc.gridy = 1;
    77     root.add(new JLabel(I18n.tr("Image key")), gbc);
    78 
    79     gbc.fill = GridBagConstraints.HORIZONTAL;
    80     gbc.weightx = 0.5;
    81     gbc.gridx = 0;
    82     gbc.gridy = 2;
    83     root.add(new JLabel(I18n.tr("Sequence key")), gbc);
    84 
    85     gbc.fill = GridBagConstraints.HORIZONTAL;
    86     gbc.gridx = 1;
    87     gbc.gridy = 0;
    88     root.add(new StreetsideButton(imgLinkAction, true), gbc);
    89 
    90     gbc.fill = GridBagConstraints.HORIZONTAL;
    91     gbc.weightx = 0.5;
    92     gbc.gridx = 1;
    93     gbc.gridy = 1;
    94     root.add(imgKeyValue, gbc);
    95 
    96     gbc.fill = GridBagConstraints.HORIZONTAL;
    97     gbc.weightx = 0.5;
    98     gbc.gridx = 1;
    99     gbc.gridy = 2;
    100     root.add(seqKeyValue, gbc);
    101 
    102     gbc.fill = GridBagConstraints.HORIZONTAL;
    103     gbc.weightx = 0.5;
    104     gbc.gridx = 2;
    105     gbc.gridy = 1;
    106     root.add(copyButton, gbc);
    107 
    108     createLayout(root, true, null);
    109     selectedImageChanged(null, null);
    110   }
    111 
    112   public static ImageInfoPanel getInstance() {
    113     synchronized (ImageInfoPanel.class) {
    114       if (instance == null) {
    115         instance = new ImageInfoPanel();
    116       }
    117       return instance;
    118     }
    119   }
    120 
    121   /**
    122    * Destroys the unique instance of the class.
    123    */
    124   public static synchronized void destroyInstance() {
    125     instance = null;
    126   }
    127 
    128   /* (non-Javadoc)
    129    * @see org.openstreetmap.josm.gui.dialogs.ToggleDialog#stateChanged()
    130    */
    131   @Override
    132   protected void stateChanged() {
    133     super.stateChanged();
    134     if (isDialogShowing()) { // If the user opens the dialog once, no longer show the help message
    135       StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN.put(0);
    136     }
    137   }
    138 
    139   /* (non-Javadoc)
    140    * @see org.openstreetmap.josm.plugins.streetside.StreetsideDataListener#imagesAdded()
    141    */
    142   @Override
    143   public void imagesAdded() {
    144     // Method is not needed, but enforcesd by the interface StreetsideDataListener
    145   }
    146 
    147   /* (non-Javadoc)
    148    * @see org.openstreetmap.josm.plugins.streetside.StreetsideDataListener#selectedImageChanged(org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage, org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage)
    149    */
    150   @Override
    151   public synchronized void selectedImageChanged(final StreetsideAbstractImage oldImage,
    152       final StreetsideAbstractImage newImage) {
    153     LOGGER.info(String.format("Selected Streetside image changed from %s to %s.",
    154         oldImage instanceof StreetsideImage ? oldImage.getId() : "‹none›",
    155         newImage instanceof StreetsideImage ? newImage.getId() : "‹none›"));
    156 
    157     imgKeyValue.setEnabled(newImage instanceof StreetsideImage);
    158     final String newImageKey = newImage instanceof StreetsideImage ? newImage.getId() : null;
    159     if (newImageKey != null) {
    160       imageLinkChangeListener = b -> imgLinkAction.setURL(StreetsideURL.MainWebsite.browseImage(newImageKey));
    161       imageLinkChangeListener.valueChanged(null);
    162       StreetsideProperties.IMAGE_LINK_TO_BLUR_EDITOR.addListener(imageLinkChangeListener);
    163 
    164       imgKeyValue.setText(newImageKey);
    165       copyImgKeyAction.setContents(new StringSelection(newImageKey));
    166       addStreetsideTagAction.setTag(new Tag("streetside", newImageKey));
    167     } else {
    168       if (imageLinkChangeListener != null) {
    169         StreetsideProperties.IMAGE_LINK_TO_BLUR_EDITOR.removeListener(imageLinkChangeListener);
    170         imageLinkChangeListener = null;
    171       }
    172       imgLinkAction.setURL(null);
    173 
    174       imgKeyValue.setText('‹' + I18n.tr("image has no key") + '›');
    175       copyImgKeyAction.setContents(null);
    176       addStreetsideTagAction.setTag(null);
     69        createLayout(root, true, null);
     70        selectedImageChanged(null, null);
    17771    }
    17872
    179     final boolean partOfSequence = newImage != null && newImage.getSequence() != null
    180         && newImage.getSequence().getId() != null;
    181     seqKeyValue.setEnabled(partOfSequence);
    182     if (partOfSequence) {
    183       seqKeyValue.setText(newImage.getSequence().getId());
    184     } else {
    185       seqKeyValue.setText('‹' + I18n.tr("sequence has no id") + '›');
     73    public static ImageInfoPanel getInstance() {
     74        synchronized (ImageInfoPanel.class) {
     75            if (instance == null) {
     76                instance = new ImageInfoPanel();
     77            }
     78            return instance;
     79        }
    18680    }
    187   }
    18881
    189   /* (non-Javadoc)
    190    * @see org.openstreetmap.josm.data.SelectionChangedListener#selectionChanged(java.util.Collection)
    191    */
    192   @Override
    193   public synchronized void selectionChanged(final SelectionChangeEvent event) {
    194     final Collection<? extends OsmPrimitive> sel = event.getSelection();
    195     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    196       LOGGER.log(Logging.LEVEL_DEBUG,
    197           String.format("Selection changed. %d primitives are selected.", sel == null ? 0 : sel.size()));
     82    /**
     83     * Destroys the unique instance of the class.
     84     */
     85    public static synchronized void destroyInstance() {
     86        instance = null;
    19887    }
    199     addStreetsideTagAction.setTarget(sel != null && sel.size() == 1 ? sel.iterator().next() : null);
    200   }
     88
     89    @Override
     90    protected void stateChanged() {
     91        super.stateChanged();
     92        if (isDialogShowing()) { // If the user opens the dialog once, no longer show the help message
     93            StreetsideProperties.IMAGEINFO_HELP_COUNTDOWN.put(0);
     94        }
     95    }
     96
     97    @Override
     98    public void imagesAdded() {
     99        // Method is not needed, but enforced by the interface StreetsideDataListener
     100    }
     101
     102    @Override
     103    public synchronized void selectedImageChanged(final StreetsideImage oldImage, final StreetsideImage newImage) {
     104        LOGGER.info(() -> String.format("Selected Streetside image changed from %s to %s.",
     105                oldImage != null ? oldImage.id() : "‹none›", newImage != null ? newImage.id() : "‹none›"));
     106
     107        final String newImageKey = newImage != null ? newImage.id() : null;
     108        if (newImageKey != null) {
     109            imageLinkChangeListener = b -> imgLinkAction.setURL(StreetsideURL.MainWebsite.browseImage(newImage));
     110            imageLinkChangeListener.valueChanged(null);
     111            StreetsideProperties.IMAGE_LINK_TO_BLUR_EDITOR.addListener(imageLinkChangeListener);
     112
     113        } else {
     114            if (imageLinkChangeListener != null) {
     115                StreetsideProperties.IMAGE_LINK_TO_BLUR_EDITOR.removeListener(imageLinkChangeListener);
     116                imageLinkChangeListener = null;
     117            }
     118            imgLinkAction.setURL(null);
     119        }
     120    }
     121
     122    @Override
     123    public synchronized void selectionChanged(final SelectionChangeEvent event) {
     124        final Collection<? extends OsmPrimitive> sel = event.getSelection();
     125        if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     126            LOGGER.log(Logging.LEVEL_DEBUG,
     127                    "Selection changed. {0} primitives are selected.", sel == null ? 0 : sel.size());
     128        }
     129    }
    201130}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/StreetsideViewerPanel.java

    r36194 r36228  
    44import java.awt.BorderLayout;
    55import java.awt.GraphicsEnvironment;
    6 import java.text.MessageFormat;
     6import java.io.Serial;
    77import java.util.logging.Logger;
     8import java.util.regex.Pattern;
    89
    910import javax.swing.JCheckBox;
     
    1213
    1314import org.openstreetmap.josm.data.preferences.AbstractProperty.ValueChangeListener;
    14 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    1515import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener;
    1616import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
     
    2626import org.openstreetmap.josm.tools.Logging;
    2727
     28/**
     29 * The panel to view 360 images in
     30 */
    2831public final class StreetsideViewerPanel extends JPanel implements StreetsideDataListener {
    2932
    30   private static final long serialVersionUID = 4141847503072417190L;
     33    @Serial
     34    private static final long serialVersionUID = 4141847503072417190L;
    3135
    32   private static final Logger LOGGER = Logger.getLogger(StreetsideViewerPanel.class.getCanonicalName());
    33   private static ThreeSixtyDegreeViewerPanel threeSixtyDegreeViewerPanel;
    34   private JCheckBox highResImageryCheck;
    35   private WebLinkAction imgLinkAction;
    36   private ImageReloadAction imgReloadAction;
    37   private ValueChangeListener<Boolean> imageLinkChangeListener;
    38   private StreetsideViewerHelpPopup streetsideViewerHelp;
     36    private static final Logger LOGGER = Logger.getLogger(StreetsideViewerPanel.class.getCanonicalName());
     37    private static ThreeSixtyDegreeViewerPanel threeSixtyDegreeViewerPanel;
     38    private WebLinkAction imgLinkAction;
     39    private ValueChangeListener<Boolean> imageLinkChangeListener;
    3940
    40   public StreetsideViewerPanel() {
     41    /**
     42     * Create a new 360 viewer
     43     */
     44    public StreetsideViewerPanel() {
     45        super(new BorderLayout());
    4146
    42     super(new BorderLayout());
     47        SwingUtilities.invokeLater(this::initializeAndStartGUI);
    4348
    44     SwingUtilities.invokeLater(this::initializeAndStartGUI);
     49        selectedImageChanged(null, null);
    4550
    46     selectedImageChanged(null, null);
    47 
    48     setToolTipText(
    49         I18n.tr("Select Microsoft Streetside from the Imagery menu, then click on a blue vector bubble.."));
    50   }
    51 
    52   public static CubemapBox getCubemapBox() {
    53     return threeSixtyDegreeViewerPanel.getCubemapBox();
    54   }
    55 
    56   /**
    57    * @return the threeSixtyDegreeViewerPanel
    58    */
    59   public static ThreeSixtyDegreeViewerPanel getThreeSixtyDegreeViewerPanel() {
    60     return threeSixtyDegreeViewerPanel;
    61   }
    62 
    63   private void initializeAndStartGUI() {
    64 
    65     threeSixtyDegreeViewerPanel = new ThreeSixtyDegreeViewerPanel();
    66 
    67     if (!GraphicsEnvironment.isHeadless()) {
    68       GraphicsUtils.PlatformHelper.run(threeSixtyDegreeViewerPanel::initialize);
     51        setToolTipText(
     52                I18n.tr("Select Microsoft Streetside from the Imagery menu, then click on a blue vector bubble.."));
    6953    }
    7054
    71     add(threeSixtyDegreeViewerPanel, BorderLayout.CENTER);
    72     revalidate();
    73     repaint();
    74     JPanel checkPanel = new JPanel();
     55    /**
     56     * Get the {@link CubemapBox} for showing images
     57     * @return The box for images
     58     */
     59    public static CubemapBox getCubemapBox() {
     60        return threeSixtyDegreeViewerPanel.getCubemapBox();
     61    }
    7562
    76     imgReloadAction = new ImageReloadAction("Reload");
     63    /**
     64     * Get the current 360 viewer panel
     65     * @return the threeSixtyDegreeViewerPanel
     66     */
     67    public static ThreeSixtyDegreeViewerPanel getThreeSixtyDegreeViewerPanel() {
     68        return threeSixtyDegreeViewerPanel;
     69    }
    7770
    78     StreetsideButton imgReloadButton = new StreetsideButton(imgReloadAction);
    79 
    80     highResImageryCheck = new JCheckBox("High resolution");
    81     highResImageryCheck.setSelected(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get());
    82     highResImageryCheck.addActionListener(
    83         action -> StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.put(highResImageryCheck.isSelected()));
    84     StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.addListener(valueChange -> highResImageryCheck
    85         .setSelected(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()));
    86     checkPanel.add(highResImageryCheck, BorderLayout.WEST);
    87     checkPanel.add(imgReloadButton, BorderLayout.EAST);
    88 
    89     JPanel privacyLink = new JPanel();
    90 
    91     imgLinkAction = new WebLinkAction("Report a privacy concern with this image", null);
    92     privacyLink.add(new StreetsideButton(imgLinkAction, true));
    93     checkPanel.add(privacyLink, BorderLayout.PAGE_END);
    94 
    95     add(threeSixtyDegreeViewerPanel, BorderLayout.CENTER);
    96 
    97     JPanel bottomPanel = new JPanel();
    98     bottomPanel.add(checkPanel, BorderLayout.NORTH);
    99     bottomPanel.add(privacyLink, BorderLayout.SOUTH);
    100 
    101     add(bottomPanel, BorderLayout.PAGE_END);
    102   }
    103 
    104   /*
    105    * (non-Javadoc)
    106    *
    107    * @see
    108    * org.openstreetmap.josm.plugins.streetside.StreetsideDataListener#imagesAdded(
    109    * )
    110    */
    111   @Override
    112   public void imagesAdded() {
    113     // Method is not needed, but enforcesd by the interface StreetsideDataListener
    114   }
    115 
    116   /*
    117    * (non-Javadoc)
    118    *
    119    * @see org.openstreetmap.josm.plugins.streetside.StreetsideDataListener#
    120    * selectedImageChanged(org.openstreetmap.josm.plugins.streetside.
    121    * StreetsideAbstractImage,
    122    * org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage)
    123    */
    124   @Override
    125   public synchronized void selectedImageChanged(final StreetsideAbstractImage oldImage,
    126       final StreetsideAbstractImage newImage) {
    127 
    128     // method is invoked with null initially by framework
    129     if (newImage != null) {
    130 
    131       LOGGER.info(String.format("Selected Streetside image changed from %s to %s.",
    132           oldImage instanceof StreetsideImage ? oldImage.getId() : "‹none›",
    133           newImage instanceof StreetsideImage ? newImage.getId() : "‹none›"));
    134 
    135       final String newImageId = CubemapBuilder.getInstance().getCubemap() != null
    136           ? CubemapBuilder.getInstance().getCubemap().getId()
    137           : newImage instanceof StreetsideImage ? ((StreetsideImage) newImage).getId() : null;
    138       if (newImageId != null) {
    139         final String bubbleId = CubemapUtils.convertQuaternary2Decimal(newImageId);
    140         imageLinkChangeListener = b -> imgLinkAction
    141             .setURL(StreetsideURL.MainWebsite.streetsidePrivacyLink(bubbleId));
    142 
    143         if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    144           LOGGER.log(Logging.LEVEL_DEBUG, MessageFormat
    145               .format("Privacy link set for Streetside image {0} quadKey {1}", bubbleId, newImageId));
     71    private synchronized void initializeAndStartGUI() {
     72        try {
     73            threeSixtyDegreeViewerPanel = new ThreeSixtyDegreeViewerPanel();
     74        } catch (NoClassDefFoundError e) {
     75            Logging.trace(e);
     76            return;
    14677        }
    14778
    148         imageLinkChangeListener.valueChanged(null);
    149         StreetsideProperties.CUBEMAP_LINK_TO_BLUR_EDITOR.addListener(imageLinkChangeListener);
    150       } else {
    151         if (imageLinkChangeListener != null) {
    152           StreetsideProperties.CUBEMAP_LINK_TO_BLUR_EDITOR.removeListener(imageLinkChangeListener);
    153           imageLinkChangeListener = null;
     79        if (!GraphicsEnvironment.isHeadless()) {
     80            GraphicsUtils.PlatformHelper.run(threeSixtyDegreeViewerPanel::initialize);
    15481        }
    155         imgLinkAction.setURL(null);
    156       }
     82
     83        add(threeSixtyDegreeViewerPanel, BorderLayout.CENTER);
     84        revalidate();
     85        repaint();
     86        final var checkPanel = new JPanel();
     87
     88        final var imgReloadAction = new ImageReloadAction("Reload");
     89
     90        final var imgReloadButton = new StreetsideButton(imgReloadAction);
     91
     92        final var highResImageryCheck = new JCheckBox("High resolution");
     93        highResImageryCheck.setSelected(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get());
     94        highResImageryCheck.addActionListener(
     95                action -> StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.put(highResImageryCheck.isSelected()));
     96        StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.addListener(valueChange -> highResImageryCheck
     97                .setSelected(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()));
     98        checkPanel.add(highResImageryCheck, BorderLayout.WEST);
     99        checkPanel.add(imgReloadButton, BorderLayout.EAST);
     100
     101        final var privacyLink = new JPanel();
     102
     103        imgLinkAction = new WebLinkAction("Report a privacy concern with this image", null);
     104        privacyLink.add(new StreetsideButton(imgLinkAction, true));
     105        checkPanel.add(privacyLink, BorderLayout.PAGE_END);
     106
     107        add(threeSixtyDegreeViewerPanel, BorderLayout.CENTER);
     108
     109        final var bottomPanel = new JPanel();
     110        bottomPanel.add(checkPanel, BorderLayout.NORTH);
     111        bottomPanel.add(privacyLink, BorderLayout.SOUTH);
     112
     113        add(bottomPanel, BorderLayout.PAGE_END);
    157114    }
    158   }
     115
     116    @Override
     117    public void imagesAdded() {
     118        // Method is not needed, but enforcesd by the interface StreetsideDataListener
     119    }
     120
     121    @Override
     122    public synchronized void selectedImageChanged(final StreetsideImage oldImage, final StreetsideImage newImage) {
     123        // method is invoked with null initially by framework
     124        if (newImage != null) {
     125            LOGGER.info(() -> String.format("Selected Streetside image changed from %s to %s.",
     126                    oldImage != null ? oldImage.id() : "‹none›", newImage.id()));
     127
     128            final var newImageId = CubemapBuilder.getInstance().getCubemap() != null
     129                    ? CubemapBuilder.getInstance().getCubemap().id()
     130                    : newImage.id();
     131            if (newImageId != null) {
     132                updateLinksToNewImage(newImageId);
     133            } else {
     134                if (imageLinkChangeListener != null) {
     135                    StreetsideProperties.CUBEMAP_LINK_TO_BLUR_EDITOR.removeListener(imageLinkChangeListener);
     136                    imageLinkChangeListener = null;
     137                }
     138                imgLinkAction.setURL(null);
     139            }
     140        }
     141    }
     142
     143    private void updateLinksToNewImage(String newImageId) {
     144        final var matcher = Pattern.compile("/tiles/hs([0-9]*)").matcher(newImageId);
     145        if (matcher.find()) {
     146            final var bubbleId = CubemapUtils.convertQuaternary2Decimal(matcher.group(1));
     147            imageLinkChangeListener = b -> imgLinkAction
     148                    .setURL(StreetsideURL.MainWebsite.streetsidePrivacyLink(bubbleId));
     149
     150            if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     151                LOGGER.log(Logging.LEVEL_DEBUG, "Privacy link set for Streetside image {0} quadKey {1}",
     152                        new Object[] {bubbleId, newImageId});
     153            }
     154
     155            imageLinkChangeListener.valueChanged(null);
     156            StreetsideProperties.CUBEMAP_LINK_TO_BLUR_EDITOR.addListener(imageLinkChangeListener);
     157        }
     158    }
    159159}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ThreeSixtyDegreeViewerPanel.java

    r36194 r36228  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.plugins.streetside.gui.imageinfo;
     3
     4import java.io.Serial;
    35
    46import org.openstreetmap.josm.plugins.streetside.cubemap.CameraTransformer;
     
    1416import javafx.scene.control.Label;
    1517import javafx.scene.control.TextArea;
    16 import javafx.scene.image.Image;
    1718import javafx.scene.input.KeyCode;
     19import javafx.scene.input.KeyEvent;
    1820import javafx.scene.input.MouseEvent;
    1921import javafx.scene.layout.VBox;
     
    2224import javafx.scene.text.FontWeight;
    2325
    24 @SuppressWarnings("restriction")
     26/**
     27 * Create a panel for viewing cube mapped 360 iamges
     28 */
    2529public class ThreeSixtyDegreeViewerPanel extends JFXPanel {
    2630
    27   private static final long serialVersionUID = -4940350009018422000L;
    28   private static final CameraTransformer cameraTransform = new CameraTransformer();
    29   private static final double cameraDistance = 5000;
    30   private static Scene cubemapScene;
    31   private static Scene defaultScene;
    32   private static Scene loadingScene;
    33   private static Group root;
    34   private static Group subGroup;
    35   private static CubemapBox cubemapBox;
    36   private static PerspectiveCamera camera;
    37   private static double mousePosX;
    38   private static double mousePosY;
    39   private static double mouseOldX;
    40   private static double mouseOldY;
    41   private static double mouseDeltaX;
    42   private static double mouseDeltaY;
    43   private static Image front;
    44   private static Image right;
    45   private static Image back;
    46   private static Image left;
    47   private static Image up;
    48   private static Image down;
    49 
    50   public ThreeSixtyDegreeViewerPanel() {
    51 
    52   }
    53 
    54   private static Scene createDefaultScene() {
    55 
    56     TextArea textArea = new TextArea();
    57     textArea.setText("No Streetside image selected.");
    58 
    59     VBox vbox = new VBox(textArea);
    60 
    61     root = new Group();
    62 
    63     camera = new PerspectiveCamera(true);
    64     cameraTransform.setTranslate(0, 0, 0);
    65     cameraTransform.getChildren().addAll(camera);
    66     camera.setNearClip(0.1);
    67     camera.setFarClip(1000000.0);
    68     camera.setFieldOfView(42);
    69     camera.setTranslateZ(-cameraDistance);
    70     final PointLight light = new PointLight(Color.WHITE);
    71 
    72     cameraTransform.getChildren().add(light);
    73     light.setTranslateX(camera.getTranslateX());
    74     light.setTranslateY(camera.getTranslateY());
    75     light.setTranslateZ(camera.getTranslateZ());
    76 
    77     root.getChildren().add(cameraTransform);
    78 
    79     final double size = 100000D;
    80 
    81     cubemapBox = new CubemapBox(null, null, null, null, null, null, size, camera);
    82 
    83     subGroup = new Group();
    84     subGroup.getChildren().add(cameraTransform);
    85 
    86     cubemapScene = new Scene(new Group(root), 1024, 668, true, SceneAntialiasing.BALANCED);
    87     cubemapScene.setFill(Color.TRANSPARENT);
    88     cubemapScene.setCamera(camera);
    89 
    90     cubemapScene.setOnKeyPressed(event -> {
    91       double change = 10.0;
    92       if (event.isShiftDown()) {
    93         change = 50.0;
    94       }
    95       final KeyCode keycode = event.getCode();
    96 
    97       if (keycode == KeyCode.W) {
    98         camera.setTranslateZ(camera.getTranslateZ() + change);
    99       }
    100       if (keycode == KeyCode.S) {
    101         camera.setTranslateZ(camera.getTranslateZ() - change);
    102       }
    103 
    104       if (keycode == KeyCode.A) {
    105         camera.setTranslateX(camera.getTranslateX() - change);
    106       }
    107       if (keycode == KeyCode.D) {
    108         camera.setTranslateX(camera.getTranslateX() + change);
    109       }
    110     });
    111 
    112     cubemapScene.setOnMousePressed((MouseEvent me) -> {
    113       mousePosX = me.getSceneX();
    114       mousePosY = me.getSceneY();
    115       mouseOldX = me.getSceneX();
    116       mouseOldY = me.getSceneY();
    117     });
    118     cubemapScene.setOnMouseDragged((MouseEvent me) -> {
    119       mouseOldX = mousePosX;
    120       mouseOldY = mousePosY;
    121       mousePosX = me.getSceneX();
    122       mousePosY = me.getSceneY();
    123       mouseDeltaX = mousePosX - mouseOldX;
    124       mouseDeltaY = mousePosY - mouseOldY;
    125 
    126       double modifier = 10.0;
    127       final double modifierFactor = 0.1;
    128 
    129       if (me.isControlDown()) {
    130         modifier = 0.1;
    131       }
    132       if (me.isShiftDown()) {
    133         modifier = 50.0;
    134       }
    135       if (me.isPrimaryButtonDown()) {
    136         cameraTransform.ry.setAngle(
    137             ((cameraTransform.ry.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540)
    138                 % 360 - 180); // +
    139         cameraTransform.rx.setAngle(
    140             ((cameraTransform.rx.getAngle() - mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540)
    141                 % 360 - 180); // -
    142 
    143       } else if (me.isSecondaryButtonDown()) {
    144         final double z = camera.getTranslateZ();
    145         final double newZ = z + mouseDeltaX * modifierFactor * modifier;
    146         camera.setTranslateZ(newZ);
    147       } else if (me.isMiddleButtonDown()) {
    148         cameraTransform.t.setX(cameraTransform.t.getX() + mouseDeltaX * modifierFactor * modifier * 0.3); // -
    149         cameraTransform.t.setY(cameraTransform.t.getY() + mouseDeltaY * modifierFactor * modifier * 0.3); // -
    150       }
    151     });
    152 
    153     root.getChildren().addAll(cubemapBox, subGroup);
    154     root.setAutoSizeChildren(true);
    155 
    156     subGroup.setAutoSizeChildren(true);
    157 
    158     // prevent content from disappearing after resizing
    159     Platform.setImplicitExit(false);
    160 
    161     defaultScene = new Scene(vbox, 200, 100);
    162     return defaultScene;
    163   }
    164 
    165   private static void createLoadingScene() {
    166     Label label = new Label(" Loading...");
    167     label.setFont(Font.font(null, FontWeight.BOLD, 14));
    168     VBox vbox = new VBox(label);
    169     loadingScene = new Scene(vbox, 200, 100);
    170   }
    171 
    172   public void initialize() {
    173 
    174     root = new Group();
    175 
    176     camera = new PerspectiveCamera(true);
    177     cameraTransform.setTranslate(0, 0, 0);
    178     cameraTransform.getChildren().addAll(camera);
    179     camera.setNearClip(0.1);
    180     camera.setFarClip(1000000.0);
    181     camera.setFieldOfView(42);
    182     camera.setTranslateZ(-cameraDistance);
    183     final PointLight light = new PointLight(Color.WHITE);
    184 
    185     cameraTransform.getChildren().add(light);
    186     light.setTranslateX(camera.getTranslateX());
    187     light.setTranslateY(camera.getTranslateY());
    188     light.setTranslateZ(camera.getTranslateZ());
    189 
    190     root.getChildren().add(cameraTransform);
    191 
    192     final double size = 100000D;
    193 
    194     cubemapBox = new CubemapBox(front, right, back, left, up, down, size, camera);
    195 
    196     subGroup = new Group();
    197     subGroup.getChildren().add(cameraTransform);
    198 
    199     createLoadingScene();
    200 
    201     Platform.runLater(() -> setScene(createDefaultScene()));
    202   }
    203 
    204   public CubemapBox getCubemapBox() {
    205     if (cubemapBox == null) {
    206       // shouldn't happen
    207       initialize();
    208     }
    209     return cubemapBox;
    210   }
    211 
    212   public Scene getDefaultScene() {
    213     return defaultScene;
    214   }
    215 
    216   public Scene getCubemapScene() {
    217     return cubemapScene;
    218   }
    219 
    220   public Scene getLoadingScene() {
    221     return loadingScene;
    222   }
     31    @Serial
     32    private static final long serialVersionUID = -7032369684012156320L;
     33    private static final CameraTransformer cameraTransform = new CameraTransformer();
     34    private static final double CAMERA_DISTANCE = 5000;
     35    private static Scene cubemapScene;
     36    private static Scene defaultScene;
     37    private static Scene loadingScene;
     38    private static Group root;
     39    private static Group subGroup;
     40    private static CubemapBox cubemapBox;
     41    private static PerspectiveCamera camera;
     42    private static double mousePosX;
     43    private static double mousePosY;
     44    private static double mouseOldX;
     45    private static double mouseOldY;
     46
     47    /**
     48     * Create the default scene
     49     * @return The default scene (pretty much to tell the user that nothing is selected)
     50     */
     51    private static Scene createDefaultScene() {
     52
     53        final var textArea = new TextArea();
     54        textArea.setText("No Streetside image selected.");
     55
     56        final var vbox = new VBox(textArea);
     57
     58        initializeStatic();
     59
     60        cubemapScene = new Scene(new Group(root), 1024, 668, true, SceneAntialiasing.BALANCED);
     61        cubemapScene.setFill(Color.TRANSPARENT);
     62        cubemapScene.setCamera(camera);
     63
     64        cubemapScene.setOnKeyPressed(ThreeSixtyDegreeViewerPanel::keyPressed);
     65        cubemapScene.setOnMousePressed(ThreeSixtyDegreeViewerPanel::mouseClicked);
     66        cubemapScene.setOnMouseDragged(ThreeSixtyDegreeViewerPanel::mouseDragged);
     67
     68        root.getChildren().addAll(cubemapBox, subGroup);
     69        root.setAutoSizeChildren(true);
     70
     71        subGroup.setAutoSizeChildren(true);
     72
     73        // prevent content from disappearing after resizing
     74        Platform.setImplicitExit(false);
     75
     76        defaultScene = new Scene(vbox, 200, 100);
     77        return defaultScene;
     78    }
     79
     80    private static void keyPressed(KeyEvent event) {
     81        var change = 10.0;
     82        if (event.isShiftDown()) {
     83            change = 50.0;
     84        }
     85        final var keycode = event.getCode();
     86
     87        if (keycode == KeyCode.W) {
     88            camera.setTranslateZ(camera.getTranslateZ() + change);
     89        }
     90        if (keycode == KeyCode.S) {
     91            camera.setTranslateZ(camera.getTranslateZ() - change);
     92        }
     93
     94        if (keycode == KeyCode.A) {
     95            camera.setTranslateX(camera.getTranslateX() - change);
     96        }
     97        if (keycode == KeyCode.D) {
     98            camera.setTranslateX(camera.getTranslateX() + change);
     99        }
     100    }
     101
     102    private static void mouseClicked(MouseEvent me) {
     103        mousePosX = me.getSceneX();
     104        mousePosY = me.getSceneY();
     105        mouseOldX = me.getSceneX();
     106        mouseOldY = me.getSceneY();
     107    }
     108
     109    private static void mouseDragged(MouseEvent me) {
     110        mouseOldX = mousePosX;
     111        mouseOldY = mousePosY;
     112        mousePosX = me.getSceneX();
     113        mousePosY = me.getSceneY();
     114        final double mouseDeltaX = mousePosX - mouseOldX;
     115        final double mouseDeltaY = mousePosY - mouseOldY;
     116
     117        var modifier = 0.375;
     118        final var modifierFactor = 0.1;
     119
     120        if (me.isControlDown()) {
     121            modifier = 0.1;
     122        }
     123        if (me.isShiftDown()) {
     124            modifier = 50.0;
     125        }
     126        if (me.isSecondaryButtonDown()) { // JOSM viewer uses right-click for moving.
     127            cameraTransform.setRy(
     128                    ((cameraTransform.ry.getAngle() - mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540)
     129                            % 360 - 180); // +
     130            cameraTransform.setRx(
     131                    ((cameraTransform.rx.getAngle() + mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540)
     132                            % 360 - 180); // -
     133        } else if (me.isPrimaryButtonDown()) {
     134            final double z = camera.getTranslateZ();
     135            final double newZ = z + mouseDeltaX * modifierFactor * modifier;
     136            camera.setTranslateZ(newZ);
     137        } else if (me.isMiddleButtonDown()) {
     138            cameraTransform.setTx(cameraTransform.t.getX() + mouseDeltaX * modifierFactor * modifier * 0.3); // -
     139            cameraTransform.setTy(cameraTransform.t.getY() + mouseDeltaY * modifierFactor * modifier * 0.3); // -
     140        }
     141    }
     142
     143    private static void createLoadingScene() {
     144        final var label = new Label(" Loading...");
     145        label.setFont(Font.font(null, FontWeight.BOLD, 14));
     146        final var vbox = new VBox(label);
     147        loadingScene = new Scene(vbox, 200, 100);
     148    }
     149
     150    private static void initializeStatic() {
     151        root = new Group();
     152
     153        camera = new PerspectiveCamera(true);
     154        cameraTransform.setTranslate(0, 0, 0);
     155        cameraTransform.getChildren().addAll(camera);
     156        camera.setNearClip(0.1);
     157        camera.setFarClip(1000000.0);
     158        camera.setFieldOfView(42);
     159        camera.setTranslateZ(-CAMERA_DISTANCE);
     160        final var light = new PointLight(Color.WHITE);
     161
     162        cameraTransform.getChildren().add(light);
     163        light.setTranslateX(camera.getTranslateX());
     164        light.setTranslateY(camera.getTranslateY());
     165        light.setTranslateZ(camera.getTranslateZ());
     166
     167        root.getChildren().add(cameraTransform);
     168
     169        cubemapBox = new CubemapBox(null, null, null, null, null, null, 100_000d, camera);
     170
     171        subGroup = new Group();
     172        subGroup.getChildren().add(cameraTransform);
     173    }
     174
     175    void initialize() {
     176        initializeStatic();
     177        createLoadingScene();
     178        Platform.runLater(() -> setScene(createDefaultScene()));
     179    }
     180
     181    public CubemapBox getCubemapBox() {
     182        if (cubemapBox == null) {
     183            // shouldn't happen
     184            initialize();
     185        }
     186        return cubemapBox;
     187    }
     188
     189    public Scene getDefaultScene() {
     190        return defaultScene;
     191    }
     192
     193    public Scene getCubemapScene() {
     194        return cubemapScene;
     195    }
     196
     197    public Scene getLoadingScene() {
     198        return loadingScene;
     199    }
    223200}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/WebLinkAction.java

    r36194 r36228  
    33
    44import java.awt.event.ActionEvent;
    5 import java.io.IOException;
     5import java.io.Serial;
     6import java.net.URISyntaxException;
    67import java.net.URL;
    78import java.util.logging.Logger;
     
    1112
    1213import org.openstreetmap.josm.gui.Notification;
    13 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideUtils;
    1414import org.openstreetmap.josm.tools.ImageProvider;
    1515import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
    1616import org.openstreetmap.josm.tools.Logging;
     17import org.openstreetmap.josm.tools.OpenBrowser;
    1718
     19/**
     20 * Open an image on Microsoft's Streetside website
     21 */
    1822public class WebLinkAction extends AbstractAction {
    1923
    20   private static final long serialVersionUID = -8168227661356480455L;
     24    @Serial
     25    private static final long serialVersionUID = 6157320554869780625L;
    2126
    22   private static final Logger LOGGER = Logger.getLogger(WebLinkAction.class.getCanonicalName());
     27    private static final Logger LOGGER = Logger.getLogger(WebLinkAction.class.getCanonicalName());
    2328
    24   private URL url;
     29    private URL url;
    2530
    26   public WebLinkAction(final String name, final URL url) {
    27     super(name, ImageProvider.get("link", ImageSizes.SMALLICON));
    28     setURL(url);
    29   }
     31    /**
     32     * Create a new web link
     33     * @param name The name to show the user
     34     * @param url The URL to open
     35     */
     36    public WebLinkAction(final String name, final URL url) {
     37        super(name, ImageProvider.get("link", ImageSizes.SMALLICON));
     38        setURL(url);
     39    }
    3040
    31   /**
    32    * @param url the url to set
    33    */
    34   public final void setURL(URL url) {
    35     this.url = url;
    36     setEnabled(url != null);
    37   }
     41    /**
     42     * Set the URL for this action
     43     * @param url the url to set
     44     */
     45    public final void setURL(URL url) {
     46        this.url = url;
     47        setEnabled(url != null);
     48    }
    3849
    39   /* (non-Javadoc)
    40    * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
    41    */
    42   @Override
    43   public void actionPerformed(ActionEvent e) {
    44     try {
    45       StreetsideUtils.browse(url);
    46     } catch (IOException e1) {
    47       String msg = "Could not open the URL " + url == null ? "‹null›" : url + " in a browser";
    48       LOGGER.log(Logging.LEVEL_WARN, msg, e1);
    49       new Notification(msg).setIcon(JOptionPane.WARNING_MESSAGE).show();
     50    @Override
     51    public void actionPerformed(ActionEvent e) {
     52        try {
     53            if (this.url != null) {
     54                OpenBrowser.displayUrl(this.url.toURI());
     55            }
     56        } catch (URISyntaxException e1) {
     57            String msg = url + " in a browser";
     58            LOGGER.log(Logging.LEVEL_WARN, msg, e1);
     59            new Notification(msg).setIcon(JOptionPane.WARNING_MESSAGE).show();
     60        }
    5061    }
    51   }
    5262}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/BoundsDownloadRunnable.java

    r36194 r36228  
    44import java.awt.GraphicsEnvironment;
    55import java.io.IOException;
    6 import java.net.HttpURLConnection;
    76import java.net.URL;
    87import java.net.URLConnection;
     
    2019public abstract class BoundsDownloadRunnable implements Runnable {
    2120
    22   private static final Logger LOGGER = Logger.getLogger(BoundsDownloadRunnable.class.getCanonicalName());
     21    private static final Logger LOGGER = Logger.getLogger(BoundsDownloadRunnable.class.getCanonicalName());
    2322
    24   protected Bounds bounds;
     23    protected final Bounds bounds;
    2524
    26   protected BoundsDownloadRunnable(final Bounds bounds) {
    27     this.bounds = bounds;
    28   }
     25    protected BoundsDownloadRunnable(final Bounds bounds) {
     26        this.bounds = bounds;
     27    }
    2928
    30   /**
    31    * Logs information about the given connection via {@link Logger}.
    32    * If it's a {@link HttpURLConnection}, the request method, the response code and the URL itself are logged.
    33    * Otherwise only the URL is logged.
    34    *
    35    * @param con  the {@link URLConnection} for which information is logged
    36    * @param info an additional info text, which is appended to the output in braces
    37    * @throws IOException if {@link HttpURLConnection#getResponseCode()} throws an {@link IOException}
    38    */
    39   public static void logConnectionInfo(final URLConnection con, final String info) throws IOException {
    40     final StringBuilder message;
    41     if (con instanceof HttpURLConnection) {
    42       message = new StringBuilder(((HttpURLConnection) con).getRequestMethod()).append(' ').append(con.getURL())
    43           .append(" → ").append(((HttpURLConnection) con).getResponseCode());
    44     } else {
    45       message = new StringBuilder("Download from ").append(con.getURL());
     29    protected abstract Function<Bounds, URL> getUrlGenerator();
     30
     31    @Override
     32    public void run() {
     33        URL nextURL = getUrlGenerator().apply(bounds);
     34        if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     35            LOGGER.log(Logging.LEVEL_DEBUG, "Downloading bounds: URL: {0}", nextURL);
     36        }
     37        try {
     38            while (nextURL != null) {
     39                if (Thread.interrupted()) {
     40                    LOGGER.log(Logging.LEVEL_ERROR, "{0} for {1} interrupted!",
     41                            new Object[] { getClass().getSimpleName(), bounds });
     42                    return;
     43                }
     44                final URLConnection con = nextURL.openConnection();
     45                run(con);
     46                nextURL = APIv3.parseNextFromLinkHeaderValue(con.getHeaderField("Link"));
     47            }
     48        } catch (IOException e) {
     49            String message = "Could not read from URL " + nextURL + "!";
     50            LOGGER.log(Logging.LEVEL_WARN, message, e);
     51            if (!GraphicsEnvironment.isHeadless()) {
     52                new Notification(message).setIcon(StreetsidePlugin.LOGO.setSize(ImageSizes.LARGEICON).get())
     53                        .setDuration(Notification.TIME_LONG).show();
     54            }
     55        }
    4656    }
    47     if (info != null && info.length() >= 1) {
    48       message.append(" (").append(info).append(')');
    49     }
    50     LOGGER.info(message::toString);
    51   }
    5257
    53   protected abstract Function<Bounds, URL> getUrlGenerator();
    54 
    55   @Override
    56   public void run() {
    57     URL nextURL = getUrlGenerator().apply(bounds);
    58     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    59       LOGGER.log(Logging.LEVEL_DEBUG, "Downloading bounds: URL: {0}", nextURL);
    60     }
    61     try {
    62       while (nextURL != null) {
    63         if (Thread.interrupted()) {
    64           LOGGER.log(Logging.LEVEL_ERROR, "{0} for {1} interrupted!",
    65               new Object[] { getClass().getSimpleName(), bounds });
    66           return;
    67         }
    68         final URLConnection con = nextURL.openConnection();
    69         run(con);
    70         nextURL = APIv3.parseNextFromLinkHeaderValue(con.getHeaderField("Link"));
    71       }
    72     } catch (IOException e) {
    73       String message = "Could not read from URL " + nextURL + "!";
    74       LOGGER.log(Logging.LEVEL_WARN, message, e);
    75       if (!GraphicsEnvironment.isHeadless()) {
    76         new Notification(message).setIcon(StreetsidePlugin.LOGO.setSize(ImageSizes.LARGEICON).get())
    77             .setDuration(Notification.TIME_LONG).show();
    78       }
    79     }
    80   }
    81 
    82   public abstract void run(final URLConnection connection) throws IOException;
     58    public abstract void run(final URLConnection connection) throws IOException;
    8359}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/SequenceDownloadRunnable.java

    r36194 r36228  
    66import java.net.URLConnection;
    77import java.text.MessageFormat;
     8import java.time.LocalDate;
     9import java.time.LocalTime;
     10import java.time.ZoneOffset;
     11import java.time.format.DateTimeFormatter;
    812import java.util.ArrayList;
    9 import java.util.EnumSet;
    1013import java.util.List;
     14import java.util.Objects;
    1115import java.util.function.Function;
     16import java.util.logging.Level;
    1217import java.util.logging.Logger;
    1318
    1419import org.openstreetmap.josm.data.Bounds;
    15 import org.openstreetmap.josm.data.coor.LatLon;
    16 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    1720import org.openstreetmap.josm.plugins.streetside.StreetsideData;
    1821import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
    19 import org.openstreetmap.josm.plugins.streetside.StreetsideSequence;
    20 import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils;
    2122import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
    22 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideSequenceIdGenerator;
    2323import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL.APIv3;
    24 import org.openstreetmap.josm.tools.Logging;
     24import org.openstreetmap.josm.tools.JosmRuntimeException;
    2525
    2626import jakarta.json.Json;
    27 import jakarta.json.JsonException;
    2827import jakarta.json.JsonObject;
    29 import jakarta.json.JsonValue;
     28import jakarta.json.JsonString;
    3029import jakarta.json.stream.JsonParser;
    3130
     31/**
     32 * Download an area
     33 */
    3234public final class SequenceDownloadRunnable extends BoundsDownloadRunnable {
    33   private static final Logger LOG = Logger.getLogger(BoundsDownloadRunnable.class.getCanonicalName());
    34   private static final Function<Bounds, URL> URL_GEN = APIv3::searchStreetsideImages;
    35   private final StreetsideData data;
     35    private static final Logger LOG = Logger.getLogger(BoundsDownloadRunnable.class.getCanonicalName());
     36    private static final Function<Bounds, URL> URL_GEN = APIv3::searchStreetsideImages;
     37    private final StreetsideData data;
     38    private String logo;
     39    private String copyright;
    3640
    37   public SequenceDownloadRunnable(final StreetsideData data, final Bounds bounds) {
    38     super(bounds);
    39     this.data = data;
    40   }
    41 
    42   @Override
    43   public void run(final URLConnection con) throws IOException {
    44     if (Thread.interrupted()) {
    45       return;
     41    /**
     42     * Create a new downloader
     43     * @param data The data to add to
     44     * @param bounds The bounds to download
     45     */
     46    public SequenceDownloadRunnable(final StreetsideData data, final Bounds bounds) {
     47        super(bounds);
     48        this.data = data;
    4649    }
    4750
    48     StreetsideSequence seq = new StreetsideSequence(StreetsideSequenceIdGenerator.generateId());
     51    @Override
     52    public void run(final URLConnection con) throws IOException {
     53        if (Thread.interrupted()) {
     54            return;
     55        }
    4956
    50     List<StreetsideImage> bubbleImages = new ArrayList<>();
     57        final long startTime = System.currentTimeMillis();
    5158
    52     final long startTime = System.currentTimeMillis();
     59        // Structure is
     60        // { "authenticationResultCode": "foo", "brandLogoUri": "bar", "copyright": "text", "resource
     61        try (JsonParser parser = Json.createParser(con.getInputStream())) {
     62            if (!parser.hasNext() || parser.next() != JsonParser.Event.START_OBJECT) {
     63                throw new IllegalStateException("Expected an object");
     64            }
    5365
    54     try (JsonParser parser = Json.createParser(con.getInputStream())) {
    55       if (!parser.hasNext() || parser.next() != JsonParser.Event.START_ARRAY) {
    56         throw new IllegalStateException("Expected an array");
    57       }
    58 
    59       StreetsideImage previous = null;
    60 
    61       while (parser.hasNext() && parser.next() == JsonParser.Event.START_OBJECT) {
    62         // read everything from this START_OBJECT to the matching END_OBJECT
    63         // and return it as a tree model ObjectNode
    64         JsonObject node = parser.getObject();
    65         // Discard the first sequence ('enabled') - it does not contain bubble data
    66         if (node.get("id") != null && node.get("la") != null && node.get("lo") != null) {
    67           StreetsideImage image = new StreetsideImage(
    68               CubemapUtils.convertDecimal2Quaternary(node.getJsonNumber("id").longValue()),
    69               new LatLon(node.getJsonNumber("la").doubleValue(), node.getJsonNumber("lo").doubleValue()),
    70               node.getJsonNumber("he").doubleValue());
    71           if (previous != null) {
    72             image.setPr(Long.parseLong(previous.getId()));
    73             previous.setNe(Long.parseLong(image.getId()));
    74 
    75           }
    76           previous = image;
    77           if (node.containsKey("ad"))
    78             image.setAd(node.getJsonNumber("ad").intValue());
    79           if (node.containsKey("al"))
    80             image.setAl(node.getJsonNumber("al").doubleValue());
    81           if (node.containsKey("bl"))
    82             image.setBl(node.getString("bl"));
    83           if (node.containsKey("ml"))
    84             image.setMl(node.getJsonNumber("ml").intValue());
    85           if (node.containsKey("ne"))
    86             image.setNe(node.getJsonNumber("ne").longValue());
    87           if (node.containsKey("pi"))
    88             image.setPi(node.getJsonNumber("pi").doubleValue());
    89           if (node.containsKey("pr"))
    90             image.setPr(node.getJsonNumber("pr").longValue());
    91           if (node.containsKey("ro"))
    92             image.setRo(node.getJsonNumber("ro").doubleValue());
    93           if (node.containsKey("nbn"))
    94               image.setNbn(node.getJsonArray("nbn").getValuesAs(JsonValue::toString));
    95           if (node.containsKey("pbn"))
    96               image.setPbn(node.getJsonArray("pbn").getValuesAs(JsonValue::toString));
    97 
    98           // Add list of cubemap tile images to images
    99           List<StreetsideImage> tiles = new ArrayList<>();
    100 
    101           EnumSet.allOf(CubemapUtils.CubemapFaces.class).forEach(face -> {
    102 
    103             for (int i = 0; i < 4; i++) {
    104               // Initialize four-tiled cubemap faces (four images per cube side with 18-length
    105               // Quadkey)
    106               if (Boolean.FALSE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())) {
    107                 StreetsideImage tile = new StreetsideImage(image.getId() + i);
    108                 tiles.add(tile);
    109               }
    110               // Initialize four-tiled cubemap faces (four images per cub eside with 20-length
    111               // Quadkey)
    112               if (Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())) {
    113                 for (int j = 0; j < 4; j++) {
    114                   StreetsideImage tile = new StreetsideImage(image.getId() + face.getValue()
    115                       + CubemapUtils.rowCol2StreetsideCellAddressMap
    116                           .get(i + Integer.toString(j)));
    117                   tiles.add(tile);
    118                 }
    119               }
    120             }
    121           });
    122 
    123           bubbleImages.add(image);
    124           LOG.info("Added image with id <" + image.getId() + ">");
    125           if (Boolean.TRUE.equals(StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get())) {
    126             StreetsideData.downloadSurroundingCubemaps(image);
    127           }
    128         } else {
    129           LOG.info(MessageFormat.format("Unparsable JSON node object: {0}", node));
     66            parseJson(parser);
     67            final long endTime = System.currentTimeMillis();
     68            LOG.log(Level.INFO, "Successfully loaded {0} Microsoft Streetside images in {1} seconds.",
     69                    new Object[] {this.data.getImages().size(), (endTime - startTime) / 1000});
    13070        }
    131       }
    132     } catch (ClassCastException | JsonException e) {
    133       LOG.log(Logging.LEVEL_ERROR, e, () -> MessageFormat
    134           .format("JSON parsing error occurred during Streetside sequence download {0}", e.getMessage()));
    135     } catch (IOException e) {
    136       LOG.log(Logging.LEVEL_ERROR, e, () -> MessageFormat
    137           .format("Input/output error occurred during Streetside sequence download {0}", e.getMessage()));
    13871    }
    13972
    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 (Boolean.TRUE.equals(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);
     73    void parseJson(JsonParser parser) {
     74        while (parser.hasNext()) {
     75            if (Objects.requireNonNull(parser.next()) == JsonParser.Event.KEY_NAME) {
     76                switch (parser.getString()) {
     77                case "errorDetails" -> parseErrorDetails(parser);
     78                case "resourceSets" -> parseResourceSets(parser);
     79                case "brandLogoUri" -> parseBrandLogoUri(parser);
     80                case "copyright" -> parseCopyright(parser);
     81                default -> { /* Do nothing for now */ }
     82                }
     83            }
    15484        }
    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       }
    16485    }
    16586
    166     final long endTime = System.currentTimeMillis();
    167     LOG.info(MessageFormat.format("Successfully loaded {0} Microsoft Streetside images in {1} seconds.",
    168         seq.getImages().size(), (endTime - startTime) / 1000));
    169   }
     87    private static void parseErrorDetails(JsonParser parser) {
     88        if (parser.next() == JsonParser.Event.START_ARRAY) {
     89            final var errors = new StringBuilder();
     90            while (parser.next() != JsonParser.Event.END_ARRAY) {
     91                if (parser.currentEvent() == JsonParser.Event.VALUE_STRING) {
     92                    errors.append(parser.getString()).append('\n');
     93                }
     94            }
     95            throw new JosmRuntimeException(errors.toString());
     96        }
     97    }
    17098
    171   @Override
    172   protected Function<Bounds, URL> getUrlGenerator() {
    173     return URL_GEN;
    174   }
     99    private void parseResourceSets(JsonParser parser) {
     100        if (parser.next() == JsonParser.Event.START_ARRAY) {
     101            while (parser.hasNext() && parser.next() == JsonParser.Event.START_OBJECT) {
     102                while (parser.hasNext() && parser.currentEvent() != JsonParser.Event.END_OBJECT) {
     103                    if (parser.next() == JsonParser.Event.KEY_NAME
     104                            && "resources".equals(parser.getString())) {
     105                        parser.next();
     106                        List<StreetsideImage> bubbleImages = new ArrayList<>();
     107                        parseResource(parser, bubbleImages);
     108                        this.data.addAll(bubbleImages, true);
     109                    }
     110                }
     111            }
     112        }
     113    }
     114
     115    private void parseBrandLogoUri(JsonParser parser) {
     116        if (parser.next() == JsonParser.Event.VALUE_STRING) {
     117            this.logo = parser.getString();
     118        }
     119    }
     120
     121    private void parseCopyright(JsonParser parser) {
     122        if (parser.next() == JsonParser.Event.VALUE_STRING) {
     123            this.copyright = parser.getString();
     124        }
     125    }
     126
     127    private void parseResource(JsonParser parser, List<StreetsideImage> bubbleImages) {
     128        while (parser.hasNext() && parser.next() == JsonParser.Event.START_OBJECT) {
     129            // read everything from this START_OBJECT to the matching END_OBJECT
     130            // and return it as a tree model ObjectNode
     131            JsonObject node = parser.getObject();
     132            // Discard the first sequence ('enabled') - it does not contain bubble data
     133            if (node.get("imageUrl") != null && node.get("lat") != null && node.get("lon") != null) {
     134                final var id = node.getString("imageUrl");
     135                final var lat = node.getJsonNumber("lat").doubleValue();
     136                final var lon = node.getJsonNumber("lon").doubleValue();
     137                final var heading = node.getJsonNumber("he").doubleValue();
     138                final var pitch = node.containsKey("pi") ? node.getJsonNumber("pi").doubleValue() : Double.NaN;
     139                final var roll = node.containsKey("ro") ? node.getJsonNumber("ro").doubleValue() : Double.NaN;
     140                final var vintageStart = LocalDate
     141                        .parse(node.getString("vintageStart").replace("GMT", "UTC"),
     142                                DateTimeFormatter.ofPattern("dd LLL yyyy zzz"))
     143                        .atStartOfDay().toInstant(ZoneOffset.UTC);
     144                final var vintageEnd = LocalDate
     145                        .parse(node.getString("vintageStart").replace("GMT", "UTC"),
     146                                DateTimeFormatter.ofPattern("dd LLL yyyy zzz"))
     147                        .atTime(LocalTime.MAX).toInstant(ZoneOffset.UTC);
     148                final List<String> imageUrlSubdomains = node.getJsonArray("imageUrlSubdomains")
     149                        .getValuesAs(JsonString.class).stream().map(JsonString::getString).toList();
     150                final var zoomMax = node.getInt("zoomMax");
     151                final var zoomMin = node.getInt("zoomMin");
     152                final var imageHeight = node.getInt("imageHeight");
     153                final var imageWidth = node.getInt("imageWidth");
     154                final var image = new StreetsideImage(id, lat, lon, heading, pitch, roll, vintageStart,
     155                        vintageEnd, this.logo, this.copyright, zoomMin, zoomMax, imageHeight, imageWidth,
     156                        imageUrlSubdomains);
     157                bubbleImages.add(image);
     158                LOG.info(() -> "Added image with id <" + image.id() + ">");
     159                if (Boolean.TRUE.equals(StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get())) {
     160                    this.data.downloadSurroundingCubemaps(image);
     161                }
     162            } else {
     163                LOG.info(() -> MessageFormat.format("Unparsable JSON node object: {0}", node));
     164            }
     165        }
     166    }
     167
     168    @Override
     169    protected Function<Bounds, URL> getUrlGenerator() {
     170        return URL_GEN;
     171    }
    175172}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/StreetsideDownloader.java

    r36194 r36228  
    2525public final class StreetsideDownloader {
    2626
    27   private static final Logger LOGGER = Logger.getLogger(StreetsideDownloader.class.getCanonicalName());
    28   /**
    29    * Max area to be downloaded
    30    */
    31   private static final double MAX_AREA = StreetsideProperties.MAX_DOWNLOAD_AREA.get();
    32   /**
    33    * Executor that will run the petitions.
    34    */
    35   private static ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 100, TimeUnit.SECONDS,
    36       new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.DiscardPolicy());
    37   /**
    38    * Indicates whether the last download request has been rejected because it requested an area that was too big.
    39    * Iff true, the last download has been rejected, if false, it was executed.
    40    */
    41   private static boolean stoppedDownload;
    42 
    43   private StreetsideDownloader() {
    44     // Private constructor to avoid instantiation
    45   }
    46 
    47   /**
    48    * Downloads all images of the area covered by the OSM data.
    49    */
    50   public static void downloadOSMArea() {
    51     if (MainApplication.getLayerManager().getEditLayer() == null) {
    52       return;
    53     }
    54     if (isAreaTooBig(MainApplication.getLayerManager().getEditLayer().data.getDataSourceBounds().stream()
    55         .map(Bounds::getArea).reduce(0.0, Double::sum))) {
    56       return;
    57     }
    58     MainApplication.getLayerManager().getEditLayer().data.getDataSourceBounds().stream()
    59         .filter(bounds -> !StreetsideLayer.getInstance().getData().getBounds().contains(bounds))
    60         .forEach(bounds -> {
    61           StreetsideLayer.getInstance().getData().getBounds().add(bounds);
    62           StreetsideDownloader.getImages(bounds.getMin(), bounds.getMax());
    63         });
    64   }
    65 
    66   /**
    67    * Gets all the images in a square. It downloads all the images of all the
    68    * sequences that pass through the given rectangle.
    69    *
    70    * @param minLatLon The minimum latitude and longitude of the rectangle.
    71    * @param maxLatLon The maximum latitude and longitude of the rectangle
    72    */
    73   public static void getImages(LatLon minLatLon, LatLon maxLatLon) {
    74     if (minLatLon == null || maxLatLon == null) {
    75       throw new IllegalArgumentException();
    76     }
    77     getImages(new Bounds(minLatLon, maxLatLon));
    78   }
    79 
    80   /**
    81    * Gets the images within the given bounds.
    82    *
    83    * @param bounds A {@link Bounds} object containing the area to be downloaded.
    84    */
    85   public static void getImages(Bounds bounds) {
    86     run(new StreetsideSquareDownloadRunnable(bounds));
    87   }
    88 
    89   /**
    90    * Returns the current download mode.
    91    *
    92    * @return the currently enabled {@link DOWNLOAD_MODE}
    93    */
    94   public static DOWNLOAD_MODE getMode() {
    95     return DOWNLOAD_MODE.fromPrefId(StreetsideProperties.DOWNLOAD_MODE.get());
    96   }
    97 
    98   private static void run(Runnable t) {
    99     executor.execute(t);
    100   }
    101 
    102   /**
    103    * If some part of the current view has not been downloaded, it is downloaded.
    104    */
    105   public static void downloadVisibleArea() {
    106     Bounds view = MainApplication.getMap().mapView.getRealBounds();
    107     if (isAreaTooBig(view.getArea())) {
    108       return;
    109     }
    110     if (isViewDownloaded(view)) {
    111       return;
    112     }
    113     StreetsideLayer.getInstance().getData().getBounds().add(view);
    114     getImages(view);
    115   }
    116 
    117   private static boolean isViewDownloaded(Bounds view) {
    118     int n = 15;
    119     boolean[][] inside = new boolean[n][n];
    120     for (int i = 0; i < n; i++) {
    121       for (int j = 0; j < n; j++) {
    122         if (isInBounds(new LatLon(view.getMinLat() + (view.getMaxLat() - view.getMinLat()) * ((double) i / n),
    123             view.getMinLon() + (view.getMaxLon() - view.getMinLon()) * ((double) j / n)))) {
    124           inside[i][j] = true;
    125         }
    126       }
    127     }
    128     for (int i = 0; i < n; i++) {
    129       for (int j = 0; j < n; j++) {
    130         if (!inside[i][j])
    131           return false;
    132       }
    133     }
    134     return true;
    135   }
    136 
    137   /**
    138    * Checks if the given {@link LatLon} object lies inside the bounds of the
    139    * image.
    140    *
    141    * @param latlon The coordinates to check.
    142    * @return true if it lies inside the bounds; false otherwise;
    143    */
    144   private static boolean isInBounds(LatLon latlon) {
    145     return StreetsideLayer.getInstance().getData().getBounds().parallelStream().anyMatch(b -> b.contains(latlon));
    146   }
    147 
    148   /**
    149    * Checks if the area for which Streetside images should be downloaded is too big. This means that probably
    150    * lots of Streetside images are going to be downloaded, slowing down the
    151    * program too much. A notification is shown when the download has stopped or continued.
    152    *
    153    * @param area area to check
    154    * @return {@code true} if the area is too big
    155    */
    156   private static boolean isAreaTooBig(final double area) {
    157     final boolean tooBig = area > MAX_AREA;
    158     if (!stoppedDownload && tooBig) {
    159       new Notification(I18n
    160           .tr("The Streetside layer has stopped downloading images, because the requested area is too big!")
    161           + (getMode() == DOWNLOAD_MODE.VISIBLE_AREA
    162               ? "\n" + I18n
    163                   .tr("To solve this problem, you could zoom in and load a smaller area of the map.")
    164               : (getMode() == DOWNLOAD_MODE.OSM_AREA ? "\n" + I18n.tr(
    165                   "To solve this problem, you could switch to download mode ''{0}'' and load Streetside images for a smaller portion of the map.",
    166                   DOWNLOAD_MODE.MANUAL_ONLY) : ""))).setIcon(StreetsidePlugin.LOGO.get())
    167                       .setDuration(Notification.TIME_LONG).show();
    168     }
    169     if (stoppedDownload && !tooBig) {
    170       new Notification("The Streetside layer now continues to download images…")
    171           .setIcon(StreetsidePlugin.LOGO.get()).show();
    172     }
    173     stoppedDownload = tooBig;
    174     return tooBig;
    175   }
    176 
    177   /**
    178    * Stops all running threads.
    179    */
    180   public static void stopAll() {
    181     executor.shutdownNow();
    182     try {
    183       executor.awaitTermination(30, TimeUnit.SECONDS);
    184     } catch (InterruptedException e) {
    185       LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
    186     }
    187     executor = new ThreadPoolExecutor(3, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100),
    188         new ThreadPoolExecutor.DiscardPolicy());
    189   }
    190 
    191   /**
    192    * Possible download modes.
    193    */
    194   public enum DOWNLOAD_MODE {
    195     VISIBLE_AREA("visibleArea", I18n.tr("everything in the visible area")),
    196 
    197     OSM_AREA("osmArea", I18n.tr("areas with downloaded OSM-data")),
    198 
    199     MANUAL_ONLY("manualOnly", I18n.tr("only when manually requested"));
    200 
    201     public static final DOWNLOAD_MODE DEFAULT = OSM_AREA;
    202 
    203     private final String prefId;
    204     private final String label;
    205 
    206     DOWNLOAD_MODE(String prefId, String label) {
    207       this.prefId = prefId;
    208       this.label = label;
    209     }
    210 
    211     public static DOWNLOAD_MODE fromPrefId(String prefId) {
    212       for (DOWNLOAD_MODE mode : DOWNLOAD_MODE.values()) {
    213         if (mode.getPrefId().equals(prefId)) {
    214           return mode;
    215         }
    216       }
    217       return DEFAULT;
    218     }
    219 
    220     public static DOWNLOAD_MODE fromLabel(String label) {
    221       for (DOWNLOAD_MODE mode : DOWNLOAD_MODE.values()) {
    222         if (mode.getLabel().equals(label)) {
    223           return mode;
    224         }
    225       }
    226       return DEFAULT;
    227     }
    228 
    229     /**
    230      * @return the ID that is used to represent this download mode in the JOSM preferences
    231      */
    232     public String getPrefId() {
    233       return prefId;
    234     }
    235 
    236     /**
    237      * @return the (internationalized) label describing this download mode
    238      */
    239     public String getLabel() {
    240       return label;
    241     }
    242   }
     27    private static final Logger LOGGER = Logger.getLogger(StreetsideDownloader.class.getCanonicalName());
     28    /**
     29     * Max area to be downloaded
     30     */
     31    private static final double MAX_AREA = StreetsideProperties.MAX_DOWNLOAD_AREA.get();
     32    /**
     33     * Executor that will run the petitions.
     34     */
     35    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 100, TimeUnit.SECONDS,
     36            new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.DiscardPolicy());
     37    /**
     38     * Indicates whether the last download request has been rejected because it requested an area that was too big.
     39     * Iff true, the last download has been rejected, if false, it was executed.
     40     */
     41    private static boolean stoppedDownload;
     42
     43    private StreetsideDownloader() {
     44        // Private constructor to avoid instantiation
     45    }
     46
     47    /**
     48     * Downloads all images of the area covered by the OSM data.
     49     */
     50    public static void downloadOSMArea() {
     51        if (MainApplication.getLayerManager().getEditLayer() == null) {
     52            return;
     53        }
     54        if (isAreaTooBig(MainApplication.getLayerManager().getEditLayer().data.getDataSourceBounds().stream()
     55                .map(Bounds::getArea).reduce(0.0, Double::sum))) {
     56            return;
     57        }
     58        MainApplication.getLayerManager().getEditLayer().data.getDataSourceBounds().stream()
     59                .filter(bounds -> !StreetsideLayer.getInstance().getData().getBounds().contains(bounds))
     60                .forEach(bounds -> {
     61                    StreetsideLayer.getInstance().getData().getBounds().add(bounds);
     62                    StreetsideDownloader.getImages(bounds.getMin(), bounds.getMax());
     63                });
     64    }
     65
     66    /**
     67     * Gets all the images in a square. It downloads all the images of all the
     68     * sequences that pass through the given rectangle.
     69     *
     70     * @param minLatLon The minimum latitude and longitude of the rectangle.
     71     * @param maxLatLon The maximum latitude and longitude of the rectangle
     72     */
     73    public static void getImages(LatLon minLatLon, LatLon maxLatLon) {
     74        if (minLatLon == null || maxLatLon == null) {
     75            throw new IllegalArgumentException();
     76        }
     77        getImages(new Bounds(minLatLon, maxLatLon));
     78    }
     79
     80    /**
     81     * Gets the images within the given bounds.
     82     *
     83     * @param bounds A {@link Bounds} object containing the area to be downloaded.
     84     */
     85    public static void getImages(Bounds bounds) {
     86        run(new StreetsideSquareDownloadRunnable(bounds));
     87    }
     88
     89    /**
     90     * Returns the current download mode.
     91     *
     92     * @return the currently enabled {@link DOWNLOAD_MODE}
     93     */
     94    public static DOWNLOAD_MODE getMode() {
     95        return DOWNLOAD_MODE.fromPrefId(StreetsideProperties.DOWNLOAD_MODE.get());
     96    }
     97
     98    private static void run(Runnable t) {
     99        executor.execute(t);
     100    }
     101
     102    /**
     103     * If some part of the current view has not been downloaded, it is downloaded.
     104     */
     105    public static void downloadVisibleArea() {
     106        final var view = MainApplication.getMap().mapView.getRealBounds();
     107        if (isAreaTooBig(view.getArea())) {
     108            return;
     109        }
     110        if (isViewDownloaded(view)) {
     111            return;
     112        }
     113        StreetsideLayer.getInstance().getData().getBounds().add(view);
     114        getImages(view);
     115    }
     116
     117    private static boolean isViewDownloaded(Bounds view) {
     118        final var n = 15;
     119        final var inside = new boolean[n][n];
     120        for (var i = 0; i < n; i++) {
     121            for (var j = 0; j < n; j++) {
     122                if (isInBounds(new LatLon(view.getMinLat() + (view.getMaxLat() - view.getMinLat()) * ((double) i / n),
     123                        view.getMinLon() + (view.getMaxLon() - view.getMinLon()) * ((double) j / n)))) {
     124                    inside[i][j] = true;
     125                }
     126            }
     127        }
     128        for (var i = 0; i < n; i++) {
     129            for (var j = 0; j < n; j++) {
     130                if (!inside[i][j])
     131                    return false;
     132            }
     133        }
     134        return true;
     135    }
     136
     137    /**
     138     * Checks if the given {@link LatLon} object lies inside the bounds of the
     139     * image.
     140     *
     141     * @param latlon The coordinates to check.
     142     * @return true if it lies inside the bounds; false otherwise;
     143     */
     144    private static boolean isInBounds(LatLon latlon) {
     145        return StreetsideLayer.getInstance().getData().getBounds().parallelStream().anyMatch(b -> b.contains(latlon));
     146    }
     147
     148    /**
     149     * Checks if the area for which Streetside images should be downloaded is too big. This means that probably
     150     * lots of Streetside images are going to be downloaded, slowing down the
     151     * program too much. A notification is shown when the download has stopped or continued.
     152     *
     153     * @param area area to check
     154     * @return {@code true} if the area is too big
     155     */
     156    private static boolean isAreaTooBig(final double area) {
     157        final boolean tooBig = area > MAX_AREA;
     158        if (!stoppedDownload && tooBig) {
     159            new Notification(I18n
     160                    .tr("The Streetside layer has stopped downloading images, because the requested area is too big!")
     161                    + getDownloadMessage()).setIcon(StreetsidePlugin.LOGO.get()).setDuration(Notification.TIME_LONG)
     162                            .show();
     163        }
     164        if (stoppedDownload && !tooBig) {
     165            new Notification("The Streetside layer now continues to download images…")
     166                    .setIcon(StreetsidePlugin.LOGO.get()).show();
     167        }
     168        stoppedDownload = tooBig;
     169        return tooBig;
     170    }
     171
     172    private static String getDownloadMessage() {
     173        return switch (getMode()) {
     174        case VISIBLE_AREA -> "\n"
     175                + I18n.tr("To solve this problem, you could zoom in and load a smaller area of the map.");
     176        case OSM_AREA -> "\n" + I18n.tr("To solve this problem, you could switch to download mode ''{0}'' and"
     177                + "load Streetside images for a smaller portion of the map.");
     178        case MANUAL_ONLY -> "";
     179        };
     180    }
     181
     182    /**
     183     * Stops all running threads.
     184     */
     185    public static void stopAll() {
     186        executor.shutdownNow();
     187        try {
     188            executor.awaitTermination(30, TimeUnit.SECONDS);
     189        } catch (InterruptedException e) {
     190            Thread.currentThread().interrupt();
     191            LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     192        }
     193        executor = new ThreadPoolExecutor(3, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100),
     194                new ThreadPoolExecutor.DiscardPolicy());
     195    }
     196
     197    /**
     198     * Possible download modes.
     199     */
     200    public enum DOWNLOAD_MODE {
     201        VISIBLE_AREA("visibleArea", I18n.tr("everything in the visible area")),
     202
     203        OSM_AREA("osmArea", I18n.tr("areas with downloaded OSM-data")),
     204
     205        MANUAL_ONLY("manualOnly", I18n.tr("only when manually requested"));
     206
     207        public static final DOWNLOAD_MODE DEFAULT = OSM_AREA;
     208
     209        private final String prefId;
     210        private final String label;
     211
     212        DOWNLOAD_MODE(String prefId, String label) {
     213            this.prefId = prefId;
     214            this.label = label;
     215        }
     216
     217        /**
     218         * Convert a preference value to a mode
     219         * @param prefId The preference value to convert
     220         * @return The download mode, or {@link #DEFAULT}
     221         */
     222        public static DOWNLOAD_MODE fromPrefId(String prefId) {
     223            for (DOWNLOAD_MODE mode : DOWNLOAD_MODE.values()) {
     224                if (mode.getPrefId().equals(prefId)) {
     225                    return mode;
     226                }
     227            }
     228            return DEFAULT;
     229        }
     230
     231        /**
     232         * Convert a label to a mode
     233         * @param label The label to convert
     234         * @return The download mode, or {@link #DEFAULT}
     235         */
     236        public static DOWNLOAD_MODE fromLabel(String label) {
     237            for (DOWNLOAD_MODE mode : DOWNLOAD_MODE.values()) {
     238                if (mode.getLabel().equals(label)) {
     239                    return mode;
     240                }
     241            }
     242            return DEFAULT;
     243        }
     244
     245        /**
     246         * Get the id for the mode
     247         * @return the ID that is used to represent this download mode in the JOSM preferences
     248         */
     249        public String getPrefId() {
     250            return prefId;
     251        }
     252
     253        /**
     254         * Get the label for the mode
     255         * @return the (internationalized) label describing this download mode
     256         */
     257        public String getLabel() {
     258            return label;
     259        }
     260    }
    243261}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/StreetsideSquareDownloadRunnable.java

    r36194 r36228  
    88import org.openstreetmap.josm.plugins.streetside.utils.StreetsideUtils;
    99
     10/**
     11 * Download Streetside images withing a specified bounds
     12 */
    1013public class StreetsideSquareDownloadRunnable implements Runnable {
    1114
    12   private final Bounds bounds;
     15    private final Bounds bounds;
    1316
    14   /**
    15    * Main constructor.
    16    *
    17    * @param bounds the bounds of the area that should be downloaded
    18    */
    19   public StreetsideSquareDownloadRunnable(Bounds bounds) {
    20     this.bounds = bounds;
    21   }
    22 
    23   @Override
    24   public void run() {
    25     PluginState.startDownload();
    26     StreetsideUtils.updateHelpText();
    27 
    28     // Download basic sequence data synchronously
    29     new SequenceDownloadRunnable(StreetsideLayer.getInstance().getData(), bounds).run();
    30 
    31     if (Thread.interrupted()) {
    32       return;
     17    /**
     18     * Main constructor.
     19     *
     20     * @param bounds the bounds of the area that should be downloaded
     21     */
     22    public StreetsideSquareDownloadRunnable(Bounds bounds) {
     23        this.bounds = bounds;
    3324    }
    3425
    35     // Image detections are not currently supported for Streetside (Mapillary code removed)
     26    @Override
     27    public void run() {
     28        PluginState.startDownload();
     29        StreetsideUtils.updateHelpText();
    3630
    37     PluginState.finishDownload();
     31        // Download basic sequence data synchronously
     32        new SequenceDownloadRunnable(StreetsideLayer.getInstance().getData(), bounds).run();
    3833
    39     StreetsideUtils.updateHelpText();
    40     StreetsideLayer.invalidateInstance();
    41     StreetsideMainDialog.getInstance().updateImage();
    42   }
     34        if (Thread.interrupted()) {
     35            return;
     36        }
     37
     38        // Image detections are not currently supported for Streetside (Mapillary code removed)
     39
     40        PluginState.finishDownload();
     41
     42        StreetsideUtils.updateHelpText();
     43        StreetsideLayer.invalidateInstance();
     44        StreetsideMainDialog.getInstance().updateImage();
     45    }
    4346}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/export/StreetsideExportDownloadThread.java

    r36194 r36228  
    1616import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
    1717import org.openstreetmap.josm.plugins.streetside.cache.CacheUtils;
    18 import org.openstreetmap.josm.plugins.streetside.cache.StreetsideCache;
    1918import org.openstreetmap.josm.tools.Logging;
    2019
     
    2928public class StreetsideExportDownloadThread extends Thread implements ICachedLoaderListener {
    3029
    31   private static final Logger LOGGER = Logger.getLogger(StreetsideExportDownloadThread.class.getCanonicalName());
     30    private static final Logger LOGGER = Logger.getLogger(StreetsideExportDownloadThread.class.getCanonicalName());
    3231
    33   private final ArrayBlockingQueue<BufferedImage> queue;
    34   private final ArrayBlockingQueue<StreetsideAbstractImage> queueImages;
     32    private final ArrayBlockingQueue<BufferedImage> queue;
     33    private final ArrayBlockingQueue<StreetsideAbstractImage> queueImages;
    3534
    36   private final StreetsideImage image;
     35    private final StreetsideImage image;
    3736
    38   /**
    39    * Main constructor.
    40    *
    41    * @param image     Image to be downloaded.
    42    * @param queue     Queue of {@link BufferedImage} objects for the
    43    *          {@link StreetsideExportWriterThread}.
    44    * @param queueImages Queue of {@link StreetsideAbstractImage} objects for the
    45    *          {@link StreetsideExportWriterThread}.
    46    */
    47   public StreetsideExportDownloadThread(StreetsideImage image, ArrayBlockingQueue<BufferedImage> queue,
    48       ArrayBlockingQueue<StreetsideAbstractImage> queueImages) {
    49     this.queue = queue;
    50     this.image = image;
    51     this.queueImages = queueImages;
    52   }
     37    /**
     38     * Main constructor.
     39     *
     40     * @param image     Image to be downloaded.
     41     * @param queue     Queue of {@link BufferedImage} objects for the
     42     *          {@link StreetsideExportWriterThread}.
     43     * @param queueImages Queue of {@link StreetsideAbstractImage} objects for the
     44     *          {@link StreetsideExportWriterThread}.
     45     */
     46    public StreetsideExportDownloadThread(StreetsideImage image, ArrayBlockingQueue<BufferedImage> queue,
     47            ArrayBlockingQueue<StreetsideAbstractImage> queueImages) {
     48        this.queue = queue;
     49        this.image = image;
     50        this.queueImages = queueImages;
     51    }
    5352
    54   @Override
    55   public void run() {
    56     // use "thumbnail" type here so that the tiles are not exported
    57     CacheUtils.submit(image.getId(), StreetsideCache.Type.THUMBNAIL, this);
    58   }
     53    @Override
     54    public void run() {
     55        // use "thumbnail" type here so that the tiles are not exported
     56        CacheUtils.downloadPicture(image, CacheUtils.PICTURE.THUMBNAIL, this);
     57        CacheUtils.submit(image.id(), this);
     58    }
    5959
    60   @Override
    61   public synchronized void loadingFinished(CacheEntry data, CacheEntryAttributes attributes, LoadResult result) {
    62     try {
    63       synchronized (StreetsideExportDownloadThread.class) {
    64         queue.put(ImageIO.read(new ByteArrayInputStream(data.getContent())));
    65         queueImages.put(image);
    66       }
    67     } catch (InterruptedException | IOException e) {
    68       LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     60    @Override
     61    public synchronized void loadingFinished(CacheEntry data, CacheEntryAttributes attributes, LoadResult result) {
     62        try {
     63            synchronized (StreetsideExportDownloadThread.class) {
     64                queue.put(ImageIO.read(new ByteArrayInputStream(data.getContent())));
     65                queueImages.put(image);
     66            }
     67        } catch (InterruptedException | IOException e) {
     68            LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     69        }
    6970    }
    70   }
    7171}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/export/StreetsideExportManager.java

    r36194 r36228  
    3333public class StreetsideExportManager extends PleaseWaitRunnable {
    3434
    35   private static final Logger LOGGER = Logger.getLogger(StreetsideExportManager.class.getCanonicalName());
     35    private static final Logger LOGGER = Logger.getLogger(StreetsideExportManager.class.getCanonicalName());
    3636
    37   private final ArrayBlockingQueue<BufferedImage> queue = new ArrayBlockingQueue<>(10);
    38   private final ArrayBlockingQueue<StreetsideAbstractImage> queueImages = new ArrayBlockingQueue<>(10);
     37    private final ArrayBlockingQueue<BufferedImage> queue = new ArrayBlockingQueue<>(10);
     38    private final ArrayBlockingQueue<StreetsideAbstractImage> queueImages = new ArrayBlockingQueue<>(10);
    3939
    40   private final int amount;
    41   private final Set<StreetsideAbstractImage> images;
    42   private final String path;
     40    private final int amount;
     41    private final Set<StreetsideAbstractImage> images;
     42    private final String path;
    4343
    44   private Thread writer;
    45   private ThreadPoolExecutor ex;
     44    private Thread writer;
     45    private ThreadPoolExecutor ex;
    4646
    47   /**
    48    * Main constructor.
    49    *
    50    * @param images Set of {@link StreetsideAbstractImage} objects to be exported.
    51    * @param path   Export path.
    52    */
    53   public StreetsideExportManager(Set<StreetsideAbstractImage> images, String path) {
    54     super(tr("Downloading") + "…", new PleaseWaitProgressMonitor(tr("Exporting Streetside Images")), true);
    55     this.images = images == null ? new HashSet<>() : images;
    56     this.path = path;
    57     amount = this.images.size();
    58   }
     47    /**
     48     * Main constructor.
     49     *
     50     * @param images Set of {@link StreetsideAbstractImage} objects to be exported.
     51     * @param path   Export path.
     52     */
     53    public StreetsideExportManager(Set<StreetsideAbstractImage> images, String path) {
     54        super(tr("Downloading") + "…", new PleaseWaitProgressMonitor(tr("Exporting Streetside Images")), true);
     55        this.images = images == null ? new HashSet<>() : images;
     56        this.path = path;
     57        amount = this.images.size();
     58    }
    5959
    60   @Override
    61   protected void cancel() {
    62     writer.interrupt();
    63     ex.shutdown();
    64   }
     60    @Override
     61    protected void cancel() {
     62        writer.interrupt();
     63        ex.shutdown();
     64    }
    6565
    66   @Override
    67   protected void realRun() throws IOException {
    68     // Starts a writer thread in order to write the pictures on the disk.
    69     writer = new StreetsideExportWriterThread(path, queue, queueImages, amount, getProgressMonitor());
    70     writer.start();
    71     if (path == null) {
    72       try {
    73         writer.join();
    74       } catch (InterruptedException e) {
    75         LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
    76       }
    77       return;
     66    @Override
     67    protected void realRun() throws IOException {
     68        // Starts a writer thread in order to write the pictures on the disk.
     69        writer = new StreetsideExportWriterThread(path, queue, queueImages, amount, getProgressMonitor());
     70        writer.start();
     71        if (path == null) {
     72            try {
     73                writer.join();
     74            } catch (InterruptedException e) {
     75                LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     76            }
     77            return;
     78        }
     79        ex = new ThreadPoolExecutor(20, 35, 25, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
     80        for (StreetsideAbstractImage image : images) {
     81            if (image instanceof StreetsideImage) {
     82                try {
     83                    ex.execute(new StreetsideExportDownloadThread((StreetsideImage) image, queue, queueImages));
     84                } catch (Exception e) {
     85                    LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     86                }
     87            }
     88            try {
     89                // If the queue is full, waits for it to have more space
     90                // available before executing anything else.
     91                while (ex.getQueue().remainingCapacity() == 0) {
     92                    Thread.sleep(100);
     93                }
     94            } catch (Exception e) {
     95                LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     96            }
     97        }
     98        try {
     99            writer.join();
     100        } catch (InterruptedException e) {
     101            LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     102        }
    78103    }
    79     ex = new ThreadPoolExecutor(20, 35, 25, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
    80     for (StreetsideAbstractImage image : images) {
    81       if (image instanceof StreetsideImage) {
    82         try {
    83           ex.execute(new StreetsideExportDownloadThread((StreetsideImage) image, queue, queueImages));
    84         } catch (Exception e) {
    85           LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
    86         }
    87       }
    88       try {
    89         // If the queue is full, waits for it to have more space
    90         // available before executing anything else.
    91         while (ex.getQueue().remainingCapacity() == 0) {
    92           Thread.sleep(100);
    93         }
    94       } catch (Exception e) {
    95         LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
    96       }
     104
     105    @Override
     106    protected void finish() {
    97107    }
    98     try {
    99       writer.join();
    100     } catch (InterruptedException e) {
    101       LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
    102     }
    103   }
    104 
    105   @Override
    106   protected void finish() {
    107   }
    108108}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/export/StreetsideExportWriterThread.java

    r36194 r36228  
    3434public class StreetsideExportWriterThread extends Thread {
    3535
    36   private static final Logger LOGGER = Logger.getLogger(StreetsideExportWriterThread.class.getCanonicalName());
     36    private static final Logger LOGGER = Logger.getLogger(StreetsideExportWriterThread.class.getCanonicalName());
    3737
    38   private final String path;
    39   private final ArrayBlockingQueue<BufferedImage> queue;
    40   private final ArrayBlockingQueue<StreetsideAbstractImage> queueImages;
    41   private final int amount;
    42   private final ProgressMonitor monitor;
     38    private final ArrayBlockingQueue<BufferedImage> queue;
     39    private final ArrayBlockingQueue<StreetsideAbstractImage> queueImages;
     40    private final int amount;
     41    private final ProgressMonitor monitor;
    4342
    44   /**
    45    * Main constructor.
    46    *
    47    * @param path    Path to write the pictures.
    48    * @param queue     Queue of {@link StreetsideAbstractImage} objects.
    49    * @param queueImages Queue of {@link BufferedImage} objects.
    50    * @param amount    Amount of images that are going to be exported.
    51    * @param monitor   Progress monitor.
    52    */
    53   public StreetsideExportWriterThread(String path, ArrayBlockingQueue<BufferedImage> queue,
    54       ArrayBlockingQueue<StreetsideAbstractImage> queueImages, int amount, ProgressMonitor monitor) {
    55     this.path = path;
    56     this.queue = queue;
    57     this.queueImages = queueImages;
    58     this.amount = amount;
    59     this.monitor = monitor;
    60   }
     43    /**
     44     * Main constructor.
     45     *
     46     * @param ignored    Path to write the pictures.
     47     * @param queue     Queue of {@link StreetsideAbstractImage} objects.
     48     * @param queueImages Queue of {@link BufferedImage} objects.
     49     * @param amount    Amount of images that are going to be exported.
     50     * @param monitor   Progress monitor.
     51     */
     52    public StreetsideExportWriterThread(String ignored, ArrayBlockingQueue<BufferedImage> queue,
     53            ArrayBlockingQueue<StreetsideAbstractImage> queueImages, int amount, ProgressMonitor monitor) {
     54        this.queue = queue;
     55        this.queueImages = queueImages;
     56        this.amount = amount;
     57        this.monitor = monitor;
     58    }
    6159
    62   @Override
    63   public void run() {
    64     monitor.setCustomText("Downloaded 0/" + amount);
    65     BufferedImage img;
    66     StreetsideAbstractImage mimg;
    67     String finalPath = "";
    68     for (int i = 0; i < amount; i++) {
    69       try {
    70         img = queue.take();
    71         mimg = queueImages.take();
     60    @Override
     61    public void run() {
     62        monitor.setCustomText("Downloaded 0/" + amount);
     63        BufferedImage img;
     64        StreetsideAbstractImage mimg;
     65        var finalPath = "";
     66        for (var i = 0; i < amount; i++) {
     67            try {
     68                img = queue.take();
     69                mimg = queueImages.take();
    7270
    73         // Transforms the image into a byte array.
    74         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    75         ImageIO.write(img, "jpg", outputStream);
    76         byte[] imageBytes = outputStream.toByteArray();
     71                // Transforms the image into a byte array.
     72                final var outputStream = new ByteArrayOutputStream();
     73                ImageIO.write(img, "jpg", outputStream);
     74                byte[] imageBytes = outputStream.toByteArray();
    7775
    78         // Write EXIF tags
    79         TiffOutputSet outputSet = null;
    80         TiffOutputDirectory exifDirectory;
    81         TiffOutputDirectory gpsDirectory;
    82         // If the image is imported, loads the rest of the EXIF data.
     76                // Write EXIF tags
     77                final var outputSet = new TiffOutputSet();
     78                TiffOutputDirectory exifDirectory;
     79                TiffOutputDirectory gpsDirectory;
    8380
    84         if (null == outputSet) {
    85           outputSet = new TiffOutputSet();
     81                exifDirectory = outputSet.getOrCreateExifDirectory();
     82                gpsDirectory = outputSet.getOrCreateGPSDirectory();
     83
     84                gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF);
     85                gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF,
     86                        GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF_VALUE_TRUE_NORTH);
     87
     88                gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION);
     89                gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION, RationalNumber.valueOf(mimg.heading()));
     90
     91                exifDirectory.removeField(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL);
     92
     93                outputSet.setGPSInDegrees(mimg.lon(), mimg.lat());
     94                try (OutputStream os = new BufferedOutputStream(new FileOutputStream(finalPath + ".jpg"))) {
     95                    new ExifRewriter().updateExifMetadataLossless(imageBytes, os, outputSet);
     96                }
     97            } catch (InterruptedException e) {
     98                Thread.currentThread().interrupt();
     99                LOGGER.info("Streetside export cancelled");
     100                return;
     101            } catch (IOException | ImageReadException | ImageWriteException e) {
     102                LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
     103            }
     104
     105            // Increases the progress bar.
     106            monitor.worked(PleaseWaitProgressMonitor.PROGRESS_BAR_MAX / amount);
     107            monitor.setCustomText("Downloaded " + (i + 1) + "/" + amount);
    86108        }
    87         exifDirectory = outputSet.getOrCreateExifDirectory();
    88         gpsDirectory = outputSet.getOrCreateGPSDirectory();
    89 
    90         gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF);
    91         gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF,
    92             GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF_VALUE_TRUE_NORTH);
    93 
    94         gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION);
    95         gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION, RationalNumber.valueOf(mimg.getMovingHe()));
    96 
    97         exifDirectory.removeField(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL);
    98 
    99         outputSet.setGPSInDegrees(mimg.getMovingLatLon().lon(), mimg.getMovingLatLon().lat());
    100         OutputStream os = new BufferedOutputStream(new FileOutputStream(finalPath + ".jpg"));
    101         new ExifRewriter().updateExifMetadataLossless(imageBytes, os, outputSet);
    102 
    103         os.close();
    104       } catch (InterruptedException e) {
    105         LOGGER.info("Streetside export cancelled");
    106         return;
    107       } catch (IOException | ImageReadException | ImageWriteException e) {
    108         LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);
    109       }
    110 
    111       // Increases the progress bar.
    112       monitor.worked(PleaseWaitProgressMonitor.PROGRESS_BAR_MAX / amount);
    113       monitor.setCustomText("Downloaded " + (i + 1) + "/" + amount);
    114109    }
    115   }
    116110}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/mode/AbstractMode.java

    r36194 r36228  
    33
    44import java.awt.Cursor;
    5 import java.awt.Graphics2D;
    65import java.awt.Point;
    76import java.awt.event.MouseAdapter;
    87import java.util.Calendar;
    98
    10 import org.openstreetmap.josm.data.Bounds;
    119import org.openstreetmap.josm.gui.MainApplication;
    12 import org.openstreetmap.josm.gui.MapView;
    1310import org.openstreetmap.josm.gui.NavigatableComponent.ZoomChangeListener;
    14 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
     11import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
    1512import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
    1613import org.openstreetmap.josm.plugins.streetside.io.download.StreetsideDownloader;
     
    2421public abstract class AbstractMode extends MouseAdapter implements ZoomChangeListener {
    2522
    26   private static final int DOWNLOAD_COOLDOWN = 2000;
    27   private static SemiautomaticThread semiautomaticThread = new SemiautomaticThread();
    28 
    29   /**
    30    * Cursor that should become active when this mode is activated.
    31    */
    32   public int cursor = Cursor.DEFAULT_CURSOR;
    33 
    34   /**
    35    * Resets the semiautomatic mode thread.
    36    */
    37   public static void resetThread() {
    38     semiautomaticThread.interrupt();
    39     semiautomaticThread = new SemiautomaticThread();
    40   }
    41 
    42   protected StreetsideAbstractImage getClosest(Point clickPoint) {
    43     double snapDistance = 10;
    44     double minDistance = Double.MAX_VALUE;
    45     StreetsideAbstractImage closest = null;
    46     for (StreetsideAbstractImage image : StreetsideLayer.getInstance().getData().getImages()) {
    47       Point imagePoint = MainApplication.getMap().mapView.getPoint(image.getMovingLatLon());
    48       imagePoint.setLocation(imagePoint.getX(), imagePoint.getY());
    49       double dist = clickPoint.distanceSq(imagePoint);
    50       if (minDistance > dist && clickPoint.distance(imagePoint) < snapDistance && image.isVisible()) {
    51         minDistance = dist;
    52         closest = image;
    53       }
    54     }
    55     return closest;
    56   }
    57 
    58   /**
    59    * Paint the dataset using the engine set.
    60    *
    61    * @param g   {@link Graphics2D} used for painting
    62    * @param mv  The object that can translate GeoPoints to screen coordinates.
    63    * @param box Area where painting is going to be performed
    64    */
    65   public abstract void paint(Graphics2D g, MapView mv, Bounds box);
    66 
    67   @Override
    68   public void zoomChanged() {
    69     if (StreetsideDownloader.getMode() == StreetsideDownloader.DOWNLOAD_MODE.VISIBLE_AREA) {
    70       if (!semiautomaticThread.isAlive())
    71         semiautomaticThread.start();
    72       semiautomaticThread.moved();
    73     }
    74   }
    75 
    76   private static class SemiautomaticThread extends Thread {
     23    private static final int DOWNLOAD_COOLDOWN = 2000;
     24    private static SemiautomaticThread semiautomaticThread = new SemiautomaticThread();
    7725
    7826    /**
    79      * If in semiautomatic mode, the last Epoch time when there was a download
     27     * Cursor that should become active when this mode is activated.
    8028     */
    81     private long lastDownload;
     29    public final int cursor = Cursor.DEFAULT_CURSOR;
    8230
    83     private boolean moved;
     31    /**
     32     * Resets the semiautomatic mode thread.
     33     */
     34    public static void resetThread() {
     35        semiautomaticThread.interrupt();
     36        semiautomaticThread = new SemiautomaticThread();
     37    }
     38
     39    protected StreetsideImage getClosest(Point clickPoint) {
     40        double snapDistance = 10;
     41        double minDistance = Double.MAX_VALUE;
     42        StreetsideImage closest = null;
     43        for (StreetsideImage image : StreetsideLayer.getInstance().getData().getImages()) {
     44            Point imagePoint = MainApplication.getMap().mapView.getPoint(image);
     45            imagePoint.setLocation(imagePoint.getX(), imagePoint.getY());
     46            double dist = clickPoint.distanceSq(imagePoint);
     47            if (minDistance > dist && clickPoint.distance(imagePoint) < snapDistance && image.visible()) {
     48                minDistance = dist;
     49                closest = image;
     50            }
     51        }
     52        return closest;
     53    }
    8454
    8555    @Override
    86     public void run() {
    87       while (true) {
    88         if (this.moved && Calendar.getInstance().getTimeInMillis() - this.lastDownload >= DOWNLOAD_COOLDOWN) {
    89           this.lastDownload = Calendar.getInstance().getTimeInMillis();
    90           StreetsideDownloader.downloadVisibleArea();
    91           this.moved = false;
    92           StreetsideLayer.invalidateInstance();
     56    public void zoomChanged() {
     57        if (StreetsideDownloader.getMode() == StreetsideDownloader.DOWNLOAD_MODE.VISIBLE_AREA) {
     58            if (!semiautomaticThread.isAlive())
     59                semiautomaticThread.start();
     60            semiautomaticThread.moved();
    9361        }
    94         try {
    95           Thread.sleep(100);
    96         } catch (InterruptedException e) {
    97           return;
    98         }
    99       }
    10062    }
    10163
    102     public void moved() {
    103       this.moved = true;
     64    private static class SemiautomaticThread extends Thread {
     65
     66        /**
     67         * If in semiautomatic mode, the last Epoch time when there was a download
     68         */
     69        private long lastDownload;
     70
     71        private boolean moved;
     72
     73        @Override
     74        public void run() {
     75            while (true) {
     76                if (this.moved && Calendar.getInstance().getTimeInMillis() - this.lastDownload >= DOWNLOAD_COOLDOWN) {
     77                    this.lastDownload = Calendar.getInstance().getTimeInMillis();
     78                    StreetsideDownloader.downloadVisibleArea();
     79                    this.moved = false;
     80                    StreetsideLayer.invalidateInstance();
     81                }
     82                try {
     83                    Thread.sleep(100);
     84                } catch (InterruptedException e) {
     85                    return;
     86                }
     87            }
     88        }
     89
     90        public void moved() {
     91            this.moved = true;
     92        }
    10493    }
    105   }
    10694}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/mode/SelectMode.java

    r36194 r36228  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    6 import java.awt.Graphics2D;
    7 import java.awt.Point;
    8 import java.awt.event.InputEvent;
    96import java.awt.event.MouseEvent;
    10 import java.util.Objects;
    11 import java.util.concurrent.ConcurrentSkipListSet;
    127
    13 import javax.swing.SwingUtilities;
    14 
    15 import org.openstreetmap.josm.data.Bounds;
    16 import org.openstreetmap.josm.data.coor.LatLon;
    178import org.openstreetmap.josm.data.osm.OsmPrimitive;
    189import org.openstreetmap.josm.gui.MainApplication;
    19 import org.openstreetmap.josm.gui.MapView;
    2010import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    21 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    22 import org.openstreetmap.josm.plugins.streetside.StreetsideData;
    2311import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
    2412import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
    2513import org.openstreetmap.josm.plugins.streetside.gui.StreetsideMainDialog;
    26 import org.openstreetmap.josm.plugins.streetside.history.StreetsideRecord;
    27 import org.openstreetmap.josm.plugins.streetside.history.commands.CommandMove;
    28 import org.openstreetmap.josm.plugins.streetside.history.commands.CommandTurn;
    2914import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
    3015
     
    3520 */
    3621public class SelectMode extends AbstractMode {
    37   private final StreetsideRecord record;
    38   private StreetsideAbstractImage closest;
    39   private StreetsideAbstractImage lastClicked;
    40   private boolean nothingHighlighted;
    41   private boolean imageHighlighted;
     22    private boolean nothingHighlighted;
     23    private boolean imageHighlighted;
    4224
    43   /**
    44    * Main constructor.
    45    */
    46   public SelectMode() {
    47     record = StreetsideRecord.getInstance();
    48   }
    49 
    50   @Override
    51   public void mousePressed(MouseEvent e) {
    52     if (e.getButton() != MouseEvent.BUTTON1) {
    53       return;
    54     }
    55     StreetsideAbstractImage closest = getClosest(e.getPoint());
    56     if (!(MainApplication.getLayerManager().getActiveLayer() instanceof StreetsideLayer) && closest != null
    57         && MainApplication.getMap().mapMode == MainApplication.getMap().mapModeSelect) {
    58       lastClicked = this.closest;
    59       StreetsideLayer.getInstance().getData().setSelectedImage(closest);
    60       return;
    61     } else if (MainApplication.getLayerManager().getActiveLayer() != StreetsideLayer.getInstance()) {
    62       return;
    63     }
    64     // Double click
    65     if (e.getClickCount() == 2 && StreetsideLayer.getInstance().getData().getSelectedImage() != null
    66         && closest != null) {
    67       closest.getSequence().getImages().forEach(StreetsideLayer.getInstance().getData()::addMultiSelectedImage);
    68     }
    69     lastClicked = this.closest;
    70     this.closest = closest;
    71     if (closest != null && StreetsideLayer.getInstance().getData().getMultiSelectedImages().contains(closest)) {
    72       return;
    73     }
    74     // ctrl+click
    75     if (e.getModifiers() == (InputEvent.BUTTON1_MASK | InputEvent.CTRL_MASK) && closest != null) {
    76       StreetsideLayer.getInstance().getData().addMultiSelectedImage(closest);
    77       // shift + click
    78     } else if (e.getModifiers() == (InputEvent.BUTTON1_MASK | InputEvent.SHIFT_MASK)
    79         && lastClicked instanceof StreetsideImage) {
    80       if (this.closest != null && this.closest.getSequence() == lastClicked.getSequence()) {
    81         int i = this.closest.getSequence().getImages().indexOf(this.closest);
    82         int j = lastClicked.getSequence().getImages().indexOf(lastClicked);
    83         StreetsideLayer.getInstance().getData().addMultiSelectedImage(i < j
    84             ? new ConcurrentSkipListSet<>(this.closest.getSequence().getImages().subList(i, j + 1))
    85             : new ConcurrentSkipListSet<>(this.closest.getSequence().getImages().subList(j, i + 1)));
    86       }
    87       // click
    88     } else {
    89       StreetsideLayer.getInstance().getData().setSelectedImage(closest);
    90     }
    91   }
    92 
    93   @Override
    94   public void mouseDragged(MouseEvent e) {
    95     StreetsideAbstractImage highlightImg = StreetsideLayer.getInstance().getData().getHighlightedImage();
    96     if (MainApplication.getLayerManager().getActiveLayer() == StreetsideLayer.getInstance()
    97         && SwingUtilities.isLeftMouseButton(e) && highlightImg != null && highlightImg.getLatLon() != null) {
    98       Point highlightImgPoint = MainApplication.getMap().mapView.getPoint(highlightImg.getTempLatLon());
    99       if (e.isShiftDown()) { // turn
    100         StreetsideLayer.getInstance().getData().getMultiSelectedImages().parallelStream()
    101             .filter(img -> !(img instanceof StreetsideImage) || StreetsideProperties.DEVELOPER.get())
    102             .forEach(img -> img.turn(Math.toDegrees(
    103                 Math.atan2(e.getX() - highlightImgPoint.getX(), -e.getY() + highlightImgPoint.getY()))
    104                 - highlightImg.getTempHe()));
    105       } else { // move
    106         LatLon eventLatLon = MainApplication.getMap().mapView.getLatLon(e.getX(), e.getY());
    107         LatLon imgLatLon = MainApplication.getMap().mapView.getLatLon(highlightImgPoint.getX(),
    108             highlightImgPoint.getY());
    109         StreetsideLayer.getInstance().getData().getMultiSelectedImages().parallelStream()
    110             .filter(img -> !(img instanceof StreetsideImage) || StreetsideProperties.DEVELOPER.get())
    111             .forEach(img -> img.move(eventLatLon.getX() - imgLatLon.getX(),
    112                 eventLatLon.getY() - imgLatLon.getY()));
    113       }
    114       StreetsideLayer.invalidateInstance();
    115     }
    116   }
    117 
    118   @Override
    119   public void mouseReleased(MouseEvent e) {
    120     final StreetsideData data = StreetsideLayer.getInstance().getData();
    121     if (data.getSelectedImage() == null) {
    122       return;
    123     }
    124     if (!Objects.equals(data.getSelectedImage().getTempHe(), data.getSelectedImage().getMovingHe())) {
    125       double from = data.getSelectedImage().getTempHe();
    126       double to = data.getSelectedImage().getMovingHe();
    127       record.addCommand(new CommandTurn(data.getMultiSelectedImages(), to - from));
    128     } else if (!Objects.equals(data.getSelectedImage().getTempLatLon(),
    129         data.getSelectedImage().getMovingLatLon())) {
    130       LatLon from = data.getSelectedImage().getTempLatLon();
    131       LatLon to = data.getSelectedImage().getMovingLatLon();
    132       record.addCommand(
    133           new CommandMove(data.getMultiSelectedImages(), to.getX() - from.getX(), to.getY() - from.getY()));
    134     }
    135     data.getMultiSelectedImages().parallelStream().filter(Objects::nonNull)
    136         .forEach(StreetsideAbstractImage::stopMoving);
    137     StreetsideLayer.invalidateInstance();
    138   }
    139 
    140   /**
    141    * Checks if the mouse is over pictures.
    142    */
    143   @Override
    144   public void mouseMoved(MouseEvent e) {
    145     if (MainApplication.getLayerManager().getActiveLayer() instanceof OsmDataLayer
    146         && MainApplication.getMap().mapMode != MainApplication.getMap().mapModeSelect) {
    147       return;
    148     }
    149     if (Boolean.FALSE.equals(StreetsideProperties.HOVER_ENABLED.get())) {
    150       return;
     25    @Override
     26    public void mousePressed(MouseEvent e) {
     27        if (e.getButton() != MouseEvent.BUTTON1) {
     28            return;
     29        }
     30        // click
     31        StreetsideLayer.getInstance().getData().setSelectedImage(getClosest(e.getPoint()));
    15132    }
    15233
    153     StreetsideAbstractImage closestTemp = getClosest(e.getPoint());
     34    /**
     35     * Checks if the mouse is over pictures.
     36     */
     37    @Override
     38    public void mouseMoved(MouseEvent e) {
     39        if (MainApplication.getLayerManager().getActiveLayer() instanceof OsmDataLayer
     40                && MainApplication.getMap().mapMode != MainApplication.getMap().mapModeSelect) {
     41            return;
     42        }
     43        if (Boolean.FALSE.equals(StreetsideProperties.HOVER_ENABLED.get())) {
     44            return;
     45        }
    15446
    155     final OsmDataLayer editLayer = MainApplication.getLayerManager().getEditLayer();
    156     if (editLayer != null) {
    157       if (closestTemp != null && !imageHighlighted) {
    158         if (MainApplication.getMap().mapMode != null) {
    159           MainApplication.getMap().mapMode.putValue("active", Boolean.FALSE);
     47        StreetsideImage closestTemp = getClosest(e.getPoint());
     48
     49        final OsmDataLayer editLayer = MainApplication.getLayerManager().getEditLayer();
     50        if (editLayer != null) {
     51            if (closestTemp != null && !imageHighlighted) {
     52                if (MainApplication.getMap().mapMode != null) {
     53                    MainApplication.getMap().mapMode.putValue("active", Boolean.FALSE);
     54                }
     55                imageHighlighted = true;
     56            } else if (closestTemp == null && imageHighlighted && nothingHighlighted) {
     57                if (MainApplication.getMap().mapMode != null) {
     58                    MainApplication.getMap().mapMode.putValue("active", Boolean.TRUE);
     59                }
     60                nothingHighlighted = false;
     61            } else if (imageHighlighted && !nothingHighlighted && editLayer.data != null) {
     62                for (OsmPrimitive primivitive : MainApplication.getLayerManager().getEditLayer().data.allPrimitives()) {
     63                    primivitive.setHighlighted(false);
     64                }
     65                imageHighlighted = false;
     66                nothingHighlighted = true;
     67            }
    16068        }
    161         imageHighlighted = true;
    162       } else if (closestTemp == null && imageHighlighted && nothingHighlighted) {
    163         if (MainApplication.getMap().mapMode != null) {
    164           MainApplication.getMap().mapMode.putValue("active", Boolean.TRUE);
     69
     70        if (StreetsideLayer.getInstance().getData().getHighlightedImage() != closestTemp && closestTemp != null) {
     71            StreetsideLayer.getInstance().getData().setHighlightedImage(closestTemp);
     72            StreetsideMainDialog.getInstance().setImage(closestTemp);
     73            StreetsideMainDialog.getInstance().updateImage(false);
     74
     75        } else if (StreetsideLayer.getInstance().getData().getHighlightedImage() != closestTemp
     76                && closestTemp == null) {
     77            StreetsideLayer.getInstance().getData().setHighlightedImage(null);
     78            StreetsideMainDialog.getInstance().setImage(StreetsideLayer.getInstance().getData().getSelectedImage());
     79            StreetsideMainDialog.getInstance().updateImage();
    16580        }
    166         nothingHighlighted = false;
    167       } else if (imageHighlighted && !nothingHighlighted && editLayer.data != null) {
    168         for (OsmPrimitive primivitive : MainApplication.getLayerManager().getEditLayer().data.allPrimitives()) {
    169           primivitive.setHighlighted(false);
    170         }
    171         imageHighlighted = false;
    172         nothingHighlighted = true;
    173       }
     81
     82        StreetsideLayer.invalidateInstance();
    17483    }
    17584
    176     if (StreetsideLayer.getInstance().getData().getHighlightedImage() != closestTemp && closestTemp != null) {
    177       StreetsideLayer.getInstance().getData().setHighlightedImage(closestTemp);
    178       StreetsideMainDialog.getInstance().setImage(closestTemp);
    179       StreetsideMainDialog.getInstance().updateImage(false);
    180 
    181     } else if (StreetsideLayer.getInstance().getData().getHighlightedImage() != closestTemp
    182         && closestTemp == null) {
    183       StreetsideLayer.getInstance().getData().setHighlightedImage(null);
    184       StreetsideMainDialog.getInstance().setImage(StreetsideLayer.getInstance().getData().getSelectedImage());
    185       StreetsideMainDialog.getInstance().updateImage();
     85    @Override
     86    public String toString() {
     87        return tr("Select mode");
    18688    }
    187 
    188     StreetsideLayer.invalidateInstance();
    189   }
    190 
    191   @Override
    192   public void paint(Graphics2D g, MapView mv, Bounds box) {
    193   }
    194 
    195   @Override
    196   public String toString() {
    197     return tr("Select mode");
    198   }
    19989}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/mode/package-info.java

    r34428 r36228  
    33 * The different modes that the {@link org.openstreetmap.josm.plugins.streetside.StreetsideLayer} can be in.
    44 * <br>
    5  * Currently there are two of them:
     5 * Currently, there are two of them:
    66 * <ul>
    77 *  <li><strong>{@link org.openstreetmap.josm.plugins.streetside.mode.SelectMode SelectMode}</strong> for selecting pictures in the layer</li>
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/GraphicsUtils.java

    r36194 r36228  
    55import java.awt.image.AffineTransformOp;
    66import java.awt.image.BufferedImage;
    7 import java.text.MessageFormat;
     7import java.util.Arrays;
     8import java.util.Map;
     9import java.util.Objects;
    810import java.util.logging.Logger;
    911
     12import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY;
    1013import org.openstreetmap.josm.tools.Logging;
    1114
    1215import javafx.application.Platform;
    13 import javafx.scene.image.PixelWriter;
    14 import javafx.scene.image.WritableImage;
    1516
    16 public class GraphicsUtils {
     17/**
     18 * Various graphic utilities, mostly for images.
     19 */
     20public final class GraphicsUtils {
    1721
    18   private static final Logger LOGGER = Logger.getLogger(GraphicsUtils.class.getCanonicalName());
     22    private static final Logger LOGGER = Logger.getLogger(GraphicsUtils.class.getCanonicalName());
    1923
    20   private GraphicsUtils() {
    21     // Private constructor to avoid instantiation
    22   }
    23 
    24   public static javafx.scene.image.Image convertBufferedImage2JavaFXImage(BufferedImage bf) {
    25     WritableImage res = null;
    26     if (bf != null) {
    27       res = new WritableImage(bf.getWidth(), bf.getHeight());
    28       PixelWriter pw = res.getPixelWriter();
    29       for (int x = 0; x < bf.getWidth(); x++) {
    30         for (int y = 0; y < bf.getHeight(); y++) {
    31           pw.setArgb(x, y, bf.getRGB(x, y));
    32         }
    33       }
    34     }
    35     return res;
    36   }
    37 
    38   public static BufferedImage buildMultiTiledCubemapFaceImage(final BufferedImage[] tiles) {
    39 
    40     long start = System.currentTimeMillis();
    41 
    42     BufferedImage res = null;
    43 
    44     int pixelBuffer = Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) ? 2 : 1;
    45 
    46     BufferedImage[] croppedTiles = cropMultiTiledImages(tiles, pixelBuffer);
    47 
    48     // we assume the no. of rows and cols are known and each chunk has equal width and height
    49     int rows = Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) ? 4 : 2;
    50     int cols = Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) ? 4 : 2;
    51 
    52     int chunkWidth;
    53     int chunkHeight;
    54 
    55     chunkWidth = croppedTiles[0].getWidth();
    56     chunkHeight = croppedTiles[0].getHeight();
    57 
    58     //Initializing the final image
    59     BufferedImage img = new BufferedImage(chunkWidth * cols, chunkHeight * rows, BufferedImage.TYPE_INT_ARGB);
    60 
    61     int num = 0;
    62     for (int i = 0; i < rows; i++) {
    63       for (int j = 0; j < cols; j++) {
    64         // TODO: unintended mirror image created with draw call - requires
    65         // extra reversal step - fix!
    66         img.createGraphics().drawImage(croppedTiles[num], chunkWidth * j, (chunkHeight * i), null);
    67 
    68         int width = Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) ? 1014
    69             : 510;
    70         int height = width;
    71 
    72         // BufferedImage for mirror image
    73         res = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    74 
    75         // Create mirror image pixel by pixel
    76         for (int y = 0; y < height; y++) {
    77           for (int lx = 0, rx = width - 1; lx < width; lx++, rx--) {
    78             // lx starts from the left side of the image
    79             // rx starts from the right side of the image
    80             // lx is used since we are getting pixel from left side
    81             // rx is used to set from right side
    82             // get source pixel value
    83             int p = img.getRGB(lx, y);
    84 
    85             // set mirror image pixel value
    86             res.setRGB(rx, y, p);
    87           }
    88         }
    89         num++;
    90       }
     24    private GraphicsUtils() {
     25        // Private constructor to avoid instantiation
    9126    }
    9227
    93     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    94       LOGGER.log(Logging.LEVEL_DEBUG,
    95           MessageFormat.format("Image concatenated in {0} millisecs.", (System.currentTimeMillis() - start)));
    96     }
    97     return res;
    98   }
    99 
    100   public static BufferedImage rotateImage(BufferedImage bufImg) {
    101     AffineTransform tx = AffineTransform.getScaleInstance(-1, -1);
    102     tx.translate(-bufImg.getWidth(null), -bufImg.getHeight(null));
    103     AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
    104     bufImg = op.filter(bufImg, null);
    105     return bufImg;
    106   }
    107 
    108   private static BufferedImage[] cropMultiTiledImages(BufferedImage[] tiles, int pixelBuffer) {
    109 
    110     long start = System.currentTimeMillis();
    111 
    112     BufferedImage[] res = new BufferedImage[tiles.length];
    113 
    114     for (int i = 0; i < tiles.length; i++) {
    115       if (Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())) {
    116         res[i] = tiles[i].getSubimage(pixelBuffer, pixelBuffer, 256 - pixelBuffer, 256 - pixelBuffer);
    117       } else {
    118         res[i] = tiles[i].getSubimage(pixelBuffer, pixelBuffer, 256 - pixelBuffer, 256 - pixelBuffer);
    119       }
     28    /**
     29     * Build the face given the tiles
     30     * @param tiles The tile images
     31     * @param zoom The zoom level
     32     * @return The rendered image
     33     */
     34    public static BufferedImage buildMultiTiledCubemapFaceImage(Map<CubeMapTileXY, BufferedImage> tiles, int zoom) {
     35        var faceTileImages = new BufferedImage[tiles.size()];
     36        for (var entry : tiles.entrySet()) {
     37            final var index = cubeMapTileToIndex(entry.getKey(), zoom);
     38            if (index >= faceTileImages.length) { // Due to some null tiles during loading... TODO: How is this happening?
     39                faceTileImages = Arrays.copyOf(faceTileImages, index + 1);
     40            }
     41            faceTileImages[index] = entry.getValue();
     42        }
     43        return buildMultiTiledCubemapFaceImage(faceTileImages);
    12044    }
    12145
    122     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    123       LOGGER.log(Logging.LEVEL_DEBUG,
    124           MessageFormat.format("Images cropped in {0} millisecs.", (System.currentTimeMillis() - start)));
     46    /**
     47     * Convert a tile to an index for painting
     48     * @param tile The tile to convert
     49     * @param zoom The zoom
     50     * @return The index for the array
     51     */
     52    static int cubeMapTileToIndex(CubeMapTileXY tile, int zoom) {
     53        return (1 << zoom) * tile.y() + tile.x();
    12554    }
    12655
    127     return res;
    128   }
     56    /**
     57     * Build an image given a list of tiles
     58     * @param tiles The tiles to use (note: there should be a factor of 4 images, e.g. 1, 4, 16, 64, ...)
     59     * @return The rendered image
     60     */
     61    public static BufferedImage buildMultiTiledCubemapFaceImage(final BufferedImage[] tiles) {
    12962
    130   public static class PlatformHelper {
     63        long start = System.currentTimeMillis();
    13164
    132     private PlatformHelper() {
    133       // Private constructor to avoid instantiation
     65        final int zoom = Math.toIntExact(Math.round(Math.log(tiles.length) / Math.log(4)));
     66        int pixelBuffer = zoom >= 1 ? 2 : 1;
     67
     68        BufferedImage[] croppedTiles = cropMultiTiledImages(tiles, pixelBuffer);
     69
     70        // we assume the no. of rows and cols are known and each chunk has equal width and height
     71        final int rows = Math.toIntExact(Math.round(Math.pow(2, zoom)));
     72        final int cols = rows;
     73
     74        int chunkWidth;
     75        int chunkHeight;
     76
     77        chunkWidth = Arrays.stream(croppedTiles).filter(Objects::nonNull).findFirst().orElseThrow().getWidth();
     78        chunkHeight = Arrays.stream(croppedTiles).filter(Objects::nonNull).findFirst().orElseThrow().getHeight();
     79
     80        //Initializing the final image
     81        final var img = new BufferedImage(chunkWidth * cols, chunkHeight * rows, BufferedImage.TYPE_INT_ARGB);
     82
     83        int num = 0;
     84        final var g2d = img.createGraphics();
     85        for (var i = 0; i < rows; i++) {
     86            for (var j = 0; j < cols; j++) {
     87                final var tile = croppedTiles[num++];
     88                if (tile != null) {
     89                    // TODO: unintended mirror image created with draw call - requires
     90                    // extra reversal step - fix!
     91                    g2d.drawImage(tile, chunkWidth * j, (chunkHeight * i), null);
     92                }
     93            }
     94        }
     95        g2d.dispose();
     96
     97        if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     98            LOGGER.log(Logging.LEVEL_DEBUG, "Image concatenated in {0} millisecs.",
     99                    (System.currentTimeMillis() - start));
     100        }
     101        return img;
    134102    }
    135103
    136     public static void run(Runnable treatment) {
    137       if (treatment == null)
    138         throw new IllegalArgumentException("The treatment to perform can not be null");
     104    /**
     105     * Rotate an image by 180 degrees
     106     * @param bufImg The image to rotate
     107     * @return The rotated image
     108     */
     109    public static BufferedImage rotateImage(BufferedImage bufImg) {
     110        // FIXME: Does AffineTransform.getRotateInstance(Math.PI) work? (docs indicate that this is optimized)
     111        final var tx = AffineTransform.getScaleInstance(-1, -1);
     112        tx.translate(-bufImg.getWidth(null), -bufImg.getHeight(null));
     113        final var op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
     114        bufImg = op.filter(bufImg, null);
     115        return bufImg;
     116    }
    139117
    140       if (Platform.isFxApplicationThread())
    141         treatment.run();
    142       else
    143         Platform.runLater(treatment);
     118    private static BufferedImage[] cropMultiTiledImages(BufferedImage[] tiles, int pixelBuffer) {
     119
     120        final long start = System.currentTimeMillis();
     121
     122        final var res = new BufferedImage[tiles.length];
     123
     124        for (var i = 0; i < tiles.length; i++) {
     125            if (tiles[i] != null) {
     126                res[i] = tiles[i].getSubimage(pixelBuffer, pixelBuffer, 256 - pixelBuffer, 256 - pixelBuffer);
     127            }
     128        }
     129
     130        if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     131            LOGGER.log(Logging.LEVEL_DEBUG, "Images cropped in {0} millisecs.", (System.currentTimeMillis() - start));
     132        }
     133
     134        return res;
    144135    }
    145   }
     136
     137    /**
     138     * Utilities for running in the JavaFX platform thread
     139     */
     140    public static final class PlatformHelper {
     141
     142        private PlatformHelper() {
     143            // Private constructor to avoid instantiation
     144        }
     145
     146        /**
     147         * Run a job in the JavaFX UI thread
     148         * @param treatment The runnable to run
     149         */
     150        public static void run(Runnable treatment) {
     151            if (treatment == null)
     152                throw new IllegalArgumentException("The treatment to perform can not be null");
     153
     154            if (Platform.isFxApplicationThread())
     155                treatment.run();
     156            else
     157                Platform.runLater(treatment);
     158        }
     159    }
    146160}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/ImageUtil.java

    r36194 r36228  
    77import javax.swing.ImageIcon;
    88
    9 import org.openstreetmap.josm.tools.I18n;
    109import org.openstreetmap.josm.tools.Logging;
    1110
     11/**
     12 * Various utility methods for images
     13 */
    1214public final class ImageUtil {
    1315
    14   private static final Logger LOGGER = Logger.getLogger(ImageUtil.class.getCanonicalName());
     16    private static final Logger LOGGER = Logger.getLogger(ImageUtil.class.getCanonicalName());
    1517
    16   private ImageUtil() {
    17     // Private constructor to avoid instantiation
    18   }
     18    private ImageUtil() {
     19        // Private constructor to avoid instantiation
     20    }
    1921
    20   /**
    21    * Scales an {@link ImageIcon} to the desired size
    22    *
    23    * @param icon the icon, which should be resized
    24    * @param size the desired length of the longest edge of the icon
    25    * @return the resized {@link ImageIcon}. It is the same object that you put in,
    26    * only the contained {@link Image} is exchanged.
    27    */
    28   public static ImageIcon scaleImageIcon(final ImageIcon icon, int size) {
    29     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    30       LOGGER.log(Logging.LEVEL_DEBUG, I18n.tr("Scale icon {0} → {1}", icon.getIconWidth(), size));
     22    /**
     23     * Scales an {@link ImageIcon} to the desired size
     24     *
     25     * @param icon the icon, which should be resized
     26     * @param size the desired length of the longest edge of the icon
     27     * @return the resized {@link ImageIcon}. It is the same object that you put in,
     28     * only the contained {@link Image} is exchanged.
     29     */
     30    public static ImageIcon scaleImageIcon(final ImageIcon icon, int size) {
     31        if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
     32            LOGGER.log(Logging.LEVEL_DEBUG, "Scale icon {0} → {1}", new Object[] { icon.getIconWidth(), size });
     33        }
     34        return new ImageIcon(
     35                icon.getImage()
     36                        .getScaledInstance(
     37                                icon.getIconWidth() >= icon.getIconHeight() ? size
     38                                        : Math.max(1,
     39                                                Math.round(icon.getIconWidth() / (float) icon.getIconHeight() * size)),
     40                                icon.getIconHeight() >= icon.getIconWidth() ? size
     41                                        : Math.max(1,
     42                                                Math.round(icon.getIconHeight() / (float) icon.getIconWidth() * size)),
     43                                Image.SCALE_SMOOTH));
    3144    }
    32     return new ImageIcon(
    33         icon.getImage()
    34             .getScaledInstance(
    35                 icon.getIconWidth() >= icon.getIconHeight() ? size
    36                     : Math.max(1,
    37                         Math.round(icon.getIconWidth() / (float) icon.getIconHeight() * size)),
    38                 icon.getIconHeight() >= icon.getIconWidth() ? size
    39                     : Math.max(1,
    40                         Math.round(icon.getIconHeight() / (float) icon.getIconWidth() * size)),
    41                 Image.SCALE_SMOOTH));
    42   }
    4345}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/MapViewGeometryUtil.java

    r36194 r36228  
    77import java.awt.Shape;
    88import java.awt.geom.Area;
    9 import java.awt.geom.Path2D;
    109
    1110import org.openstreetmap.josm.data.Bounds;
    1211import org.openstreetmap.josm.gui.MapView;
    13 import org.openstreetmap.josm.gui.NavigatableComponent;
    14 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    15 import org.openstreetmap.josm.plugins.streetside.StreetsideSequence;
    1612
    1713/**
    18  * Utility class to convert entities like {@link Bounds} and {@link StreetsideSequence} into {@link Shape}s that
     14 * Utility class to convert entities like {@link Bounds} into {@link Shape}s that
    1915 * can then easily be drawn on a {@link MapView}s {@link Graphics2D}-context.
    2016 */
    2117public final class MapViewGeometryUtil {
    22   private MapViewGeometryUtil() {
    23     // Private constructor to avoid instantiation
    24   }
     18    private MapViewGeometryUtil() {
     19        // Private constructor to avoid instantiation
     20    }
    2521
    26   /**
    27    * Subtracts the download bounds from the rectangular bounds of the map view.
    28    *
    29    * @param mv       the MapView that is used for the LatLon-to-Point-conversion and that determines
    30    *             the Bounds from which the downloaded Bounds are subtracted
    31    * @param downloadBounds multiple {@link Bounds} objects that represent the downloaded area
    32    * @return the difference between the {@link MapView}s bounds and the downloaded area
    33    */
    34   public static Area getNonDownloadedArea(MapView mv, Iterable<Bounds> downloadBounds) {
    35     Rectangle b = mv.getBounds();
    36     // on some platforms viewport bounds seem to be offset from the left,
    37     // over-grow it just to be sure
    38     b.grow(100, 100);
    39     Area a = new Area(b);
    40     // now successively subtract downloaded areas
    41     for (Bounds bounds : downloadBounds) {
    42       Point p1 = mv.getPoint(bounds.getMin());
    43       Point p2 = mv.getPoint(bounds.getMax());
    44       Rectangle r = new Rectangle(Math.min(p1.x, p2.x), Math.min(p1.y, p2.y), Math.abs(p2.x - p1.x),
    45           Math.abs(p2.y - p1.y));
    46       a.subtract(new Area(r));
     22    /**
     23     * Subtracts the download bounds from the rectangular bounds of the map view.
     24     *
     25     * @param mv       the MapView that is used for the LatLon-to-Point-conversion and that determines
     26     *             the Bounds from which the downloaded Bounds are subtracted
     27     * @param downloadBounds multiple {@link Bounds} objects that represent the downloaded area
     28     * @return the difference between the {@link MapView}s bounds and the downloaded area
     29     */
     30    public static Area getNonDownloadedArea(MapView mv, Iterable<Bounds> downloadBounds) {
     31        Rectangle b = mv.getBounds();
     32        // on some platforms viewport bounds seem to be offset from the left,
     33        // over-grow it just to be sure
     34        b.grow(100, 100);
     35        Area a = new Area(b);
     36        // now successively subtract downloaded areas
     37        for (Bounds bounds : downloadBounds) {
     38            Point p1 = mv.getPoint(bounds.getMin());
     39            Point p2 = mv.getPoint(bounds.getMax());
     40            Rectangle r = new Rectangle(Math.min(p1.x, p2.x), Math.min(p1.y, p2.y), Math.abs(p2.x - p1.x),
     41                    Math.abs(p2.y - p1.y));
     42            a.subtract(new Area(r));
     43        }
     44        return a;
    4745    }
    48     return a;
    49   }
    5046
    51   /**
    52    * Converts a {@link StreetsideSequence} into a {@link Path2D} that can be drawn
    53    * on the specified {@link NavigatableComponent}'s {@link Graphics2D}-context.
    54    *
    55    * @param nc  the {@link NavigatableComponent} for which this conversion should be performed, typically a {@link MapView}
    56    * @param seq the sequence to convert
    57    * @return the {@link Path2D} object to which the {@link StreetsideSequence} has been converted
    58    */
    59   public static Path2D getSequencePath(NavigatableComponent nc, StreetsideSequence seq) {
    60     final Path2D.Double path = new Path2D.Double();
    61     seq.getImages().stream().filter(StreetsideAbstractImage::isVisible).forEach(img -> {
    62       Point p = nc.getPoint(img.getMovingLatLon());
    63       if (path.getCurrentPoint() == null) {
    64         path.moveTo(p.getX(), p.getY());
    65       } else {
    66         path.lineTo(p.getX(), p.getY());
    67       }
    68     });
    69     return path;
    70   }
    7147}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/PluginState.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside.utils;
    33
    4 import static org.openstreetmap.josm.tools.I18n.tr;
    5 
    64import java.util.logging.Logger;
    75
    8 import javax.swing.JOptionPane;
    9 
    10 import org.openstreetmap.josm.gui.MainApplication;
    116import org.openstreetmap.josm.tools.I18n;
    127import org.openstreetmap.josm.tools.Logging;
    138
    149/**
     10 * The current state of the plugin (used for uploads and downloads)
    1511 * @author nokutu
    16  *
    1712 */
    1813public final class PluginState {
    1914
    20   private static final Logger LOGGER = Logger.getLogger(PluginState.class.getCanonicalName());
     15    private static final Logger LOGGER = Logger.getLogger(PluginState.class.getCanonicalName());
    2116
    22   private static boolean submittingChangeset;
     17    private static int runningDownloads;
    2318
    24   private static int runningDownloads;
    25   /**
    26    * Images that have to be uploaded.
    27    */
    28   private static int imagesToUpload;
    29   /**
    30    * Images that have been uploaded.
    31    */
    32   private static int imagesUploaded;
     19    private PluginState() {
     20        // Empty constructor to avoid instantiation
     21    }
    3322
    34   private PluginState() {
    35     // Empty constructor to avoid instantiation
    36   }
     23    /**
     24     * Called when a download is started.
     25     */
     26    public static void startDownload() {
     27        runningDownloads++;
     28    }
    3729
    38   /**
    39    * Called when a download is started.
    40    */
    41   public static void startDownload() {
    42     runningDownloads++;
    43   }
     30    /**
     31     * Called when a download is finished.
     32     */
     33    public static void finishDownload() {
     34        if (runningDownloads == 0) {
     35            LOGGER.log(Logging.LEVEL_WARN, () -> I18n.tr("The amount of running downloads is equal to 0"));
     36            return;
     37        }
     38        runningDownloads--;
     39    }
    4440
    45   /**
    46    * Called when a download is finished.
    47    */
    48   public static void finishDownload() {
    49     if (runningDownloads == 0) {
    50       LOGGER.log(Logging.LEVEL_WARN, I18n.tr("The amount of running downloads is equal to 0"));
    51       return;
     41    /**
     42     * Checks if there is any running download.
     43     *
     44     * @return true if the plugin is downloading; false otherwise.
     45     */
     46    public static boolean isDownloading() {
     47        return runningDownloads > 0;
    5248    }
    53     runningDownloads--;
    54   }
    55 
    56   /**
    57    * Checks if there is any running download.
    58    *
    59    * @return true if the plugin is downloading; false otherwise.
    60    */
    61   public static boolean isDownloading() {
    62     return runningDownloads > 0;
    63   }
    64 
    65   /**
    66    * Checks if there is a changeset being submitted.
    67    *
    68    * @return true if the plugin is submitting a changeset false otherwise.
    69    */
    70   public static boolean isSubmittingChangeset() {
    71     return submittingChangeset;
    72   }
    73 
    74   public static void setSubmittingChangeset(boolean isSubmitting) {
    75     submittingChangeset = isSubmitting;
    76   }
    77 
    78   /**
    79    * Checks if there is any running upload.
    80    *
    81    * @return true if the plugin is uploading; false otherwise.
    82    */
    83   public static boolean isUploading() {
    84     return imagesToUpload > imagesUploaded;
    85   }
    86 
    87   /**
    88    * Sets the amount of images that are going to be uploaded.
    89    *
    90    * @param amount The amount of images that are going to be uploaded.
    91    */
    92   public static void addImagesToUpload(int amount) {
    93     if (imagesToUpload <= imagesUploaded) {
    94       imagesToUpload = 0;
    95       imagesUploaded = 0;
    96     }
    97     imagesToUpload += amount;
    98   }
    99 
    100   public static int getImagesToUpload() {
    101     return imagesToUpload;
    102   }
    103 
    104   public static int getImagesUploaded() {
    105     return imagesUploaded;
    106   }
    107 
    108   /**
    109    * Called when an image is uploaded.
    110    */
    111   public static void imageUploaded() {
    112     imagesUploaded++;
    113     if (imagesToUpload == imagesUploaded) {
    114       finishedUploadDialog(imagesUploaded);
    115     }
    116   }
    117 
    118   private static void finishedUploadDialog(int numImages) {
    119     JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
    120         tr("You have successfully uploaded {0} images to Bing.com", numImages), tr("Finished upload"),
    121         JOptionPane.INFORMATION_MESSAGE);
    122   }
    123 
    124   public static void notLoggedInToMapillaryDialog() {
    125     JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
    126         tr("You are not logged in, please log in to Streetside in the preferences"),
    127         tr("Not Logged in to Streetside"), JOptionPane.WARNING_MESSAGE);
    128   }
    129 
    130   /**
    131    * Returns the text to be written in the status bar.
    132    *
    133    * @return The {@code String} that is going to be written in the status bar.
    134    */
    135   public static String getUploadString() {
    136     return tr("Uploading: {0}", "(" + imagesUploaded + "/" + imagesToUpload + ")");
    137   }
    13849}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/StreetsideColorScheme.java

    r36194 r36228  
    66import javax.swing.JComponent;
    77
     8/**
     9 * The color scheme for Streetside
     10 */
    811public final class StreetsideColorScheme {
    9   /**
    10    * Color for unselected images
    11    */
    12   public static final Color SEQ_UNSELECTED = new Color(0x00ccd1);
    13   /**
    14    * Color for the camera angle indicator of images in unselected sequences
    15    */
    16   public static final Color SEQ_UNSELECTED_CA = new Color(0x4169e1);
    17   /**
    18    * Color for the marker of images in a selected sequence
    19    */
    20   public static final Color SEQ_SELECTED = new Color(0x00b5f5);
    21   /**
    22    * Color for the camera angle indicator of images in selected sequences
    23    */
    24   public static final Color SEQ_SELECTED_CA = new Color(0x8b008b);
    25   /**
    26    * Color for the marker of currently selected images
    27    */
    28   public static final Color SEQ_HIGHLIGHTED = new Color(0xf5811a);
    29   /**
    30    * Color for the camera angle indicator of the currently selected images
    31    */
    32   public static final Color SEQ_HIGHLIGHTED_CA = new Color(0xf5b81a);
     12    /**
     13     * Color for unselected images
     14     */
     15    public static final Color SEQ_UNSELECTED = new Color(0x00ccd1);
     16    /**
     17     * Color for the camera angle indicator of images in unselected sequences
     18     */
     19    public static final Color SEQ_UNSELECTED_CA = new Color(0x4169e1);
     20    /**
     21     * Color for the marker of images in a selected sequence
     22     */
     23    public static final Color SEQ_SELECTED = new Color(0x00b5f5);
     24    /**
     25     * Color for the camera angle indicator of images in selected sequences
     26     */
     27    public static final Color SEQ_SELECTED_CA = new Color(0x8b008b);
     28    /**
     29     * Color for the marker of currently selected images
     30     */
     31    public static final Color SEQ_HIGHLIGHTED = new Color(0xf5811a);
     32    /**
     33     * Color for the camera angle indicator of the currently selected images
     34     */
     35    public static final Color SEQ_HIGHLIGHTED_CA = new Color(0xf5b81a);
    3336
    34   public static final Color SEQ_IMPORTED_SELECTED = new Color(0xdddddd);
    35   public static final Color SEQ_IMPORTED_SELECTED_CA = SEQ_IMPORTED_SELECTED.brighter();
    36   public static final Color SEQ_IMPORTED_UNSELECTED = new Color(0x999999);
    37   public static final Color SEQ_IMPORTED_UNSELECTED_CA = SEQ_IMPORTED_UNSELECTED.brighter();
    38   public static final Color SEQ_IMPORTED_HIGHLIGHTED = new Color(0xbb2222);
    39   public static final Color SEQ_IMPORTED_HIGHLIGHTED_CA = SEQ_IMPORTED_HIGHLIGHTED.brighter();
     37    public static final Color STREETSIDE_BLUE = new Color(0x0000ff);
     38    public static final Color TOOLBAR_DARK_GREY = new Color(0x242528);
    4039
    41   public static final Color STREETSIDE_BLUE = new Color(0x0000ff);
    42   public static final Color TOOLBAR_DARK_GREY = new Color(0x242528);
     40    private StreetsideColorScheme() {
     41        // Private constructor to avoid instantiation
     42    }
    4343
    44   public static final Color IMAGEDETECTION_TRAFFICSIGN = new Color(0xffc01b);
    45   public static final Color IMAGEDETECTION_UNKNOWN = new Color(0x33bb44);
    46 
    47   private StreetsideColorScheme() {
    48     // Private constructor to avoid instantiation
    49   }
    50 
    51   /**
    52    * Styles the given components as default panels (currently only the background is set to white)
    53    *
    54    * @param components the components to style as default panels (e.g. checkboxes also, that's why
    55    *           not only JPanels are accepted)
    56    */
    57   public static void styleAsDefaultPanel(JComponent... components) {
    58     if (components != null && components.length >= 1) {
    59       for (JComponent component : components) {
    60         component.setBackground(Color.WHITE);
    61       }
     44    /**
     45     * Styles the given components as default panels (currently only the background is set to white)
     46     *
     47     * @param components the components to style as default panels (e.g. checkboxes also, that's why
     48     *           not only JPanels are accepted)
     49     */
     50    public static void styleAsDefaultPanel(JComponent... components) {
     51        if (components != null && components.length >= 1) {
     52            for (JComponent component : components) {
     53                component.setBackground(Color.WHITE);
     54            }
     55        }
    6256    }
    63   }
    6457}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/StreetsideProperties.java

    r36194 r36228  
    1010import org.openstreetmap.josm.data.preferences.StringProperty;
    1111import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.ImageInfoPanel;
    12 import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.StreetsideViewerPanel;
    1312import org.openstreetmap.josm.plugins.streetside.io.download.StreetsideDownloader;
    1413
     14/**
     15 * Properties for Streetside
     16 */
    1517public final class StreetsideProperties {
    16   public static final BooleanProperty DELETE_AFTER_UPLOAD = new BooleanProperty("streetside.delete-after-upload",
    17       true);
    18   public static final BooleanProperty DEVELOPER = new BooleanProperty("streetside.developer", false);
    19   public static final BooleanProperty DISPLAY_HOUR = new BooleanProperty("streetside.display-hour", true);
    20   public static final BooleanProperty HOVER_ENABLED = new BooleanProperty("streetside.hover-enabled", true);
    21   public static final BooleanProperty MOVE_TO_IMG = new BooleanProperty("streetside.move-to-picture", true);
    22   public static final BooleanProperty TIME_FORMAT_24 = new BooleanProperty("streetside.format-24", false);
    23   public static final BooleanProperty IMAGE_LINK_TO_BLUR_EDITOR = new BooleanProperty(
    24       "streetside.image-link-to-blur-editor", true);
    25   public static final BooleanProperty CUBEMAP_LINK_TO_BLUR_EDITOR = new BooleanProperty(
    26       "streetside.cubemap-link-to-blur-editor", true);
    27   public static final IntegerProperty TILE_DOWNLOAD_THREAD_PAUSE_LEN_SEC = new IntegerProperty(
    28       "streetside.tile-download-thread-pause-len-sec", 60);
    29   public static final BooleanProperty PREDOWNLOAD_CUBEMAPS = new BooleanProperty("streetside.predownload-cubemaps",
    30       false);
    31   public static final BooleanProperty DEBUGING_ENABLED = new BooleanProperty("streetside.debugging-enabled", false);
    32   public static final BooleanProperty DOWNLOAD_CUBEFACE_TILES_TOGETHER = new BooleanProperty(
    33       "streetside.download-cubeface-tiles-together", false);
     18    public static final BooleanProperty DEVELOPER = new BooleanProperty("streetside.developer", false);
     19    public static final BooleanProperty DISPLAY_HOUR = new BooleanProperty("streetside.display-hour", true);
     20    public static final BooleanProperty HOVER_ENABLED = new BooleanProperty("streetside.hover-enabled", true);
     21    public static final BooleanProperty MOVE_TO_IMG = new BooleanProperty("streetside.move-to-picture", true);
     22    public static final BooleanProperty TIME_FORMAT_24 = new BooleanProperty("streetside.format-24", false);
     23    public static final BooleanProperty IMAGE_LINK_TO_BLUR_EDITOR = new BooleanProperty(
     24            "streetside.image-link-to-blur-editor", true);
     25    public static final BooleanProperty CUBEMAP_LINK_TO_BLUR_EDITOR = new BooleanProperty(
     26            "streetside.cubemap-link-to-blur-editor", true);
     27    public static final BooleanProperty PREDOWNLOAD_CUBEMAPS = new BooleanProperty("streetside.predownload-cubemaps",
     28            false);
     29    public static final BooleanProperty DEBUGING_ENABLED = new BooleanProperty("streetside.debugging-enabled", false);
     30    public static final BooleanProperty DOWNLOAD_CUBEFACE_TILES_TOGETHER = new BooleanProperty(
     31            "streetside.download-cubeface-tiles-together", false);
    3432
    35   /**
    36    * If false, all sequences that cross the download bounds are put completely into the StreetsideData object.
    37    * Otherwise only all images (!) inside the download bounds are added, the others are discarded.
    38    */
    39   public static final BooleanProperty CUT_OFF_SEQUENCES_AT_BOUNDS = new BooleanProperty(
    40       "streetside.cut-off-sequences-at-bounds", false);
    41   public static final IntegerProperty MAPOBJECT_ICON_SIZE = new IntegerProperty("streetside.mapobjects.iconsize", 32);
    42   public static final IntegerProperty MAX_MAPOBJECTS = new IntegerProperty("streetside.mapobjects.maximum-number",
    43       200);
    44   public static final BooleanProperty SHOW_DETECTED_SIGNS = new BooleanProperty("streetside.show-detected-signs",
    45       true);
    46   public static final BooleanProperty SHOW_HIGH_RES_STREETSIDE_IMAGERY = new BooleanProperty(
    47       "streetside.show-high-res-streetside-imagery", true);
     33    /**
     34     * If false, all sequences that cross the download bounds are put completely into the StreetsideData object.
     35     * Otherwise only all images (!) inside the download bounds are added, the others are discarded.
     36     */
     37    public static final BooleanProperty CUT_OFF_SEQUENCES_AT_BOUNDS = new BooleanProperty(
     38            "streetside.cut-off-sequences-at-bounds", false);
     39    public static final BooleanProperty SHOW_DETECTED_SIGNS = new BooleanProperty("streetside.show-detected-signs",
     40            true);
     41    public static final BooleanProperty SHOW_HIGH_RES_STREETSIDE_IMAGERY = new BooleanProperty(
     42            "streetside.show-high-res-streetside-imagery", true);
    4843
    49   /**
    50    * See {@code OsmDataLayer#PROPERTY_BACKGROUND_COLOR}
    51    */
    52   public static final NamedColorProperty BACKGROUND = new NamedColorProperty("background", Color.BLACK);
    53   /**
    54    * See {@code OsmDataLayer#PROPERTY_OUTSIDE_COLOR}
    55    */
    56   public static final NamedColorProperty OUTSIDE_DOWNLOADED_AREA = new NamedColorProperty("outside downloaded area",
    57       Color.YELLOW);
     44    /**
     45     * See {@code OsmDataLayer#PROPERTY_BACKGROUND_COLOR}
     46     */
     47    public static final NamedColorProperty BACKGROUND = new NamedColorProperty("background", Color.BLACK);
     48    /**
     49     * See {@code OsmDataLayer#PROPERTY_OUTSIDE_COLOR}
     50     */
     51    public static final NamedColorProperty OUTSIDE_DOWNLOADED_AREA = new NamedColorProperty("outside downloaded area",
     52            Color.YELLOW);
    5853
    59   public static final DoubleProperty MAX_DOWNLOAD_AREA = new DoubleProperty("streetside.max-download-area", 0.015);
     54    public static final DoubleProperty MAX_DOWNLOAD_AREA = new DoubleProperty("streetside.max-download-area", 0.015);
    6055
    61   public static final IntegerProperty PICTURE_DRAG_BUTTON = new IntegerProperty("streetside.picture-drag-button", 3);
    62   public static final IntegerProperty PICTURE_OPTION_BUTTON = new IntegerProperty("streetside.picture-option-button",
    63       2);
    64   public static final IntegerProperty PICTURE_ZOOM_BUTTON = new IntegerProperty("streetside.picture-zoom-button", 1);
    65   public static final IntegerProperty SEQUENCE_MAX_JUMP_DISTANCE = new IntegerProperty(
    66       "streetside.sequence-max-jump-distance", 100);
     56    public static final IntegerProperty PICTURE_DRAG_BUTTON = new IntegerProperty("streetside.picture-drag-button", 3);
     57    public static final IntegerProperty PICTURE_OPTION_BUTTON = new IntegerProperty("streetside.picture-option-button",
     58            2);
     59    public static final IntegerProperty PICTURE_ZOOM_BUTTON = new IntegerProperty("streetside.picture-zoom-button", 1);
     60    public static final IntegerProperty SEQUENCE_MAX_JUMP_DISTANCE = new IntegerProperty(
     61            "streetside.sequence-max-jump-distance", 100);
    6762
    68   public static final StringProperty ACCESS_TOKEN = new StringProperty("streetside.access-token", null);
    69   public static final StringProperty DOWNLOAD_MODE = new StringProperty("streetside.download-mode",
    70       StreetsideDownloader.DOWNLOAD_MODE.DEFAULT.getPrefId());
    71   public static final StringProperty START_DIR = new StringProperty("streetside.start-directory",
    72       System.getProperty("user.home"));
    73   public static final StringProperty URL_CLIENT_ID = new StringProperty("streetside.url-clientid",
    74       System.getProperty("streetside.url-clientid", "T1Fzd20xZjdtR0s1VDk5OFNIOXpYdzoxNDYyOGRkYzUyYTFiMzgz"));
    75   public static final StringProperty BING_MAPS_KEY = new StringProperty("streetside.bing-maps-key",
    76       System.getProperty("streetside.bing-maps-key", "AuftgJsO0Xs8Ts4M1xZUQJQXJNsvmh3IV8DkNieCiy3tCwCUMq76-WpkrBtNAuEm"));
    77   public static final StringProperty TEST_BUBBLE_ID = new StringProperty("streetside.test-bubble-id",
    78       System.getProperty("streetside.test-bubble-id", "80848005"));
     63    public static final StringProperty DOWNLOAD_MODE = new StringProperty("streetside.download-mode",
     64            StreetsideDownloader.DOWNLOAD_MODE.DEFAULT.getPrefId());
     65    public static final StringProperty BING_MAPS_KEY = new StringProperty("streetside.bing-maps-key",
     66            System.getProperty("streetside.bing-maps-key",
     67                    "Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU"));
    7968
    80   /**
    81    * The number of times the help popup for the {@link ImageInfoPanel} will be displayed.
    82    * But regardless of this number, the popup will only show up at most once between two (re)starts of JOSM.
    83    * Or opening the {@link ImageInfoPanel} immediately brings this number down to zero.
    84    */
    85   public static final IntegerProperty IMAGEINFO_HELP_COUNTDOWN = new IntegerProperty(
    86       "streetside.imageInfo.helpDisplayedCountdown", 4);
     69    /**
     70     * The number of times the help popup for the {@link ImageInfoPanel} will be displayed.
     71     * But regardless of this number, the popup will only show up at most once between two (re)starts of JOSM.
     72     * Or opening the {@link ImageInfoPanel} immediately brings this number down to zero.
     73     */
     74    public static final IntegerProperty IMAGEINFO_HELP_COUNTDOWN = new IntegerProperty(
     75            "streetside.imageInfo.helpDisplayedCountdown", 4);
    8776
    88   /**
    89    * The number of times the help popup for the {@link StreetsideViewerPanel} will be displayed.
    90    * But regardless of this number, the popup will only show up at most once between two (re)starts of JOSM.
    91    * Or opening the {@link StreetsideViewerPanel} immediately brings this number down to zero.
    92    */
    93   public static final IntegerProperty STREETSIDE_VIEWER_HELP_COUNTDOWN = new IntegerProperty(
    94       "streetside.streetsideViewer.helpDisplayedCountdown", 4);
     77    /**
     78     * The number of images to be prefetched when a streetside image is selected
     79     */
     80    public static final IntegerProperty PRE_FETCH_IMAGE_COUNT = new IntegerProperty("streetside.prefetch-image-count",
     81            2);
    9582
    96   /**
    97    * The number of images to be prefetched when a streetside image is selected
    98    */
    99   public static final IntegerProperty PRE_FETCH_IMAGE_COUNT = new IntegerProperty("streetside.prefetch-image-count",
    100       2);
    101 
    102   /**
    103    * The number of images to be prefetched when a streetside image is selected
    104    */
    105   public static final IntegerProperty PRE_FETCH_CUBEMAP_COUNT = new IntegerProperty("streetside.prefetch-image-count",
    106       2);
    107 
    108   private StreetsideProperties() {
    109     // Private constructor to avoid instantiation
    110   }
     83    private StreetsideProperties() {
     84        // Private constructor to avoid instantiation
     85    }
    11186}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/StreetsideURL.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside.utils;
    33
    4 import java.io.UnsupportedEncodingException;
    54import java.net.MalformedURLException;
     5import java.net.URI;
     6import java.net.URISyntaxException;
    67import java.net.URL;
    78import java.net.URLEncoder;
    89import java.nio.charset.StandardCharsets;
    910import java.text.MessageFormat;
    10 import java.util.ArrayList;
    11 import java.util.Arrays;
    12 import java.util.EnumSet;
    1311import java.util.HashMap;
    14 import java.util.List;
    1512import java.util.Map;
    16 import java.util.Map.Entry;
    1713import java.util.logging.Logger;
    1814
    1915import org.openstreetmap.josm.data.Bounds;
    20 import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils;
    21 import org.openstreetmap.josm.tools.I18n;
     16import org.openstreetmap.josm.data.coor.ILatLon;
    2217import org.openstreetmap.josm.tools.Logging;
    2318
     19/**
     20 * A class for building the base URLs for Streetside objects
     21 */
    2422public final class StreetsideURL {
    2523
    26   private static final Logger LOGGER = Logger.getLogger(StreetsideURL.class.getCanonicalName());
    27 
    28   /**
    29    * Base URL of the Bing Bubble API.
    30    */
    31   private static final String STREETSIDE_BASE_URL = "https://dev.virtualearth.net/mapcontrol/HumanScaleServices/GetBubbles.ashx";
    32   /**
    33    * Base URL for Streetside privacy concerns.
    34    */
    35   private static final String STREETSIDE_PRIVACY_URL = "https://www.bing.com/maps/privacyreport/streetsideprivacyreport?bubbleid=";
    36 
    37   private static final int OSM_BBOX_NORTH = 3;
    38   private static final int OSM_BBOX_SOUTH = 1;
    39   private static final int OSM_BBOXEAST = 2;
    40   private static final int OSM_BBOX_WEST = 0;
    41 
    42   private StreetsideURL() {
    43     // Private constructor to avoid instantiation
    44   }
    45 
    46   public static URL[] string2URLs(String baseUrlPrefix, String cubemapImageId, String baseUrlSuffix) {
    47     List<URL> res = new ArrayList<>();
    48 
    49     switch (Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) ? 16 : 4) {
    50 
    51     case 16:
    52 
    53       EnumSet.allOf(CubemapUtils.CubemapFaces.class).forEach(face -> {
    54         for (int i = 0; i < 4; i++) {
    55           for (int j = 0; j < 4; j++) {
    56             try {
    57               final String urlStr = baseUrlPrefix + cubemapImageId
    58                   + CubemapUtils.rowCol2StreetsideCellAddressMap.get(String.valueOf(i) + j)
    59                   + baseUrlSuffix;
    60               res.add(new URL(urlStr));
    61             } catch (final MalformedURLException e) {
    62               LOGGER.log(Logging.LEVEL_ERROR, "Error creating URL String for cubemap " + cubemapImageId);
    63               e.printStackTrace();
    64             }
    65 
    66           }
    67         }
    68       });
    69       break;
    70 
    71     case 4:
    72       EnumSet.allOf(CubemapUtils.CubemapFaces.class).forEach(face -> {
    73         for (int i = 0; i < 4; i++) {
    74 
    75           try {
    76             final String urlStr = baseUrlPrefix + cubemapImageId
    77                 + CubemapUtils.rowCol2StreetsideCellAddressMap.get(String.valueOf(i)) + baseUrlSuffix;
    78             res.add(new URL(urlStr));
    79           } catch (final MalformedURLException e) {
    80             LOGGER.log(Logging.LEVEL_WARN, "Error creating URL String for cubemap " + cubemapImageId);
    81             e.printStackTrace();
    82           }
    83 
    84         }
    85       });
    86       break; // break is optional
    87     default:
    88       // Statements
    89     }
    90     return res.toArray(new URL[0]);
    91   }
    92 
    93   /**
    94    * Builds a query string from it's parts that are supplied as a {@link Map}
    95    *
    96    * @param parts the parts of the query string
    97    * @return the constructed query string (including a leading ?)
    98    */
    99   static String queryString(Map<String, String> parts) {
    100     final StringBuilder ret = new StringBuilder("?client_id=").append(StreetsideProperties.URL_CLIENT_ID.get());
    101     if (parts != null) {
    102       for (final Entry<String, String> entry : parts.entrySet()) {
     24    private static final Logger LOGGER = Logger.getLogger(StreetsideURL.class.getCanonicalName());
     25
     26    /**
     27     * Base URL of the Bing Bubble API.
     28     */
     29    private static final String STREETSIDE_BASE_URL = "https://dev.virtualearth.net/REST/v1/Imagery/MetaData/Streetside";
     30    /**
     31     * Base URL for Streetside privacy concerns.
     32     */
     33    private static final String STREETSIDE_PRIVACY_URL = "https://www.bing.com/maps/privacyreport/streetsideprivacyreport?bubbleid=";
     34
     35    private static final int OSM_BBOX_NORTH = 3;
     36    private static final int OSM_BBOX_SOUTH = 1;
     37    private static final int OSM_BBOXEAST = 2;
     38    private static final int OSM_BBOX_WEST = 0;
     39
     40    private StreetsideURL() {
     41        // Private constructor to avoid instantiation
     42    }
     43
     44    /**
     45     * Convert a map of query parameters to a string
     46     * @param parts The query parameters
     47     * @return The string to send the server
     48     */
     49    static String queryStreetsideBoundsString(Map<String, String> parts) {
     50        final var ret = new StringBuilder(100);
     51        if (parts != null) {
     52            ret.append("?count=500").append("&key=").append(StreetsideProperties.BING_MAPS_KEY.get());
     53            if (parts.containsKey("bbox")) {
     54                final String[] bbox = parts.get("bbox").split(",");
     55                ret.append("&mapArea=").append(bbox[OSM_BBOX_SOUTH]).append(',').append(bbox[OSM_BBOX_WEST]).append(',')
     56                        .append(bbox[OSM_BBOX_NORTH]).append(',').append(bbox[OSM_BBOXEAST]);
     57            }
     58        }
     59
     60        return ret.toString();
     61    }
     62
     63    /**
     64     * Converts a {@link String} into a {@link URL} without throwing a {@link MalformedURLException}.
     65     * Instead, such an exception will lead to an {@link Logger}.
     66     * So you should be very confident that your URL is well-formed when calling this method.
     67     *
     68     * @param strings the Strings describing the URL
     69     * @return the URL that is constructed from the given string
     70     */
     71    static URL string2URL(String... strings) {
     72        final var builder = new StringBuilder();
     73        for (var i = 0; strings != null && i < strings.length; i++) {
     74            builder.append(strings[i]);
     75        }
    10376        try {
    104           ret.append('&').append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name())).append('=')
    105               .append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name()));
    106         } catch (final UnsupportedEncodingException e) {
    107           LOGGER.log(Logging.LEVEL_WARN, e.getMessage(), e); // This should not happen, as the encoding is hard-coded
    108         }
    109       }
    110     }
    111 
    112     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    113       LOGGER.log(Logging.LEVEL_DEBUG, MessageFormat.format("queryString result: {0}", ret));
    114     }
    115 
    116     return ret.toString();
    117   }
    118 
    119   static String queryStreetsideBoundsString(Map<String, String> parts) {
    120     final StringBuilder ret = new StringBuilder("?n=");
    121     if (parts != null) {
    122       final List<String> bbox = new ArrayList<>(Arrays.asList(parts.get("bbox").split(",")));
    123       try {
    124         ret.append(URLEncoder.encode(bbox.get(StreetsideURL.OSM_BBOX_NORTH), StandardCharsets.UTF_8.name()))
    125             .append("&s=")
    126             .append(URLEncoder.encode(bbox.get(StreetsideURL.OSM_BBOX_SOUTH),
    127                 StandardCharsets.UTF_8.name()))
    128             .append("&e=")
    129             .append(URLEncoder.encode(bbox.get(StreetsideURL.OSM_BBOXEAST), StandardCharsets.UTF_8.name()))
    130             .append("&w=")
    131             .append(URLEncoder.encode(bbox.get(StreetsideURL.OSM_BBOX_WEST), StandardCharsets.UTF_8.name()))
    132             .append("&c=1000").append("&appkey=").append(StreetsideProperties.BING_MAPS_KEY.get());
    133       } catch (final UnsupportedEncodingException e) {
    134         LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e); // This should not happen, as the encoding is hard-coded
    135       }
    136     }
    137 
    138     return ret.toString();
    139   }
    140 
    141   static String queryByIdString(Map<String, String> parts) {
    142     final StringBuilder ret = new StringBuilder("?id=");
    143     try {
    144       ret.append(URLEncoder.encode(StreetsideProperties.TEST_BUBBLE_ID.get(), StandardCharsets.UTF_8.name()));
    145       ret.append('&').append(URLEncoder.encode("appkey=", StandardCharsets.UTF_8.name())).append('=')
    146           .append(URLEncoder.encode(StreetsideProperties.BING_MAPS_KEY.get(), StandardCharsets.UTF_8.name()));
    147     } catch (final UnsupportedEncodingException e) {
    148       LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e); // This should not happen, as the encoding is hard-coded
    149     }
    150 
    151     if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    152       LOGGER.info("queryById result: " + ret);
    153     }
    154     return ret.toString();
    155   }
    156 
    157   /**
    158    * Converts a {@link String} into a {@link URL} without throwing a {@link MalformedURLException}.
    159    * Instead such an exception will lead to an {@link Logger}.
    160    * So you should be very confident that your URL is well-formed when calling this method.
    161    *
    162    * @param strings the Strings describing the URL
    163    * @return the URL that is constructed from the given string
    164    */
    165   static URL string2URL(String... strings) {
    166     final StringBuilder builder = new StringBuilder();
    167     for (int i = 0; strings != null && i < strings.length; i++) {
    168       builder.append(strings[i]);
    169     }
    170     try {
    171       return new URL(builder.toString());
    172     } catch (final MalformedURLException e) {
    173       LOGGER.log(Logging.LEVEL_ERROR, I18n.tr(String.format("The class '%s' produces malformed URLs like '%s'!",
    174           StreetsideURL.class.getName(), builder), e));
    175       return null;
    176     }
    177   }
    178 
    179   public static final class APIv3 {
    180 
    181     private APIv3() {
    182       // Private constructor to avoid instantiation
    183     }
    184 
    185     public static URL searchStreetsideImages(Bounds bounds) {
    186       return StreetsideURL.string2URL(StreetsideURL.STREETSIDE_BASE_URL, APIv3.queryStreetsideString(bounds));
    187     }
    188 
    189     /**
    190      * The APIv3 returns a Link header for each request. It contains a URL for requesting more results.
    191      * If you supply the value of the Link header, this method returns the next URL,
    192      * if such a URL is defined in the header.
    193      *
    194      * @param value the value of the HTTP-header with key "Link"
    195      * @return the {@link URL} for the next result page, or <code>null</code> if no such URL could be found
    196      */
    197     public static URL parseNextFromLinkHeaderValue(String value) {
    198       if (value != null) {
    199         // Iterate over the different entries of the Link header
    200         for (final String link : value.split(",", Integer.MAX_VALUE)) {
    201           boolean isNext = false;
    202           URL url = null;
    203           // Iterate over the parts of each entry (typically it's one `rel="‹linkType›"` and one like `<https://URL>`)
    204           for (String linkPart : link.split(";", Integer.MAX_VALUE)) {
    205             linkPart = linkPart.trim();
    206             isNext |= linkPart.matches("rel\\s*=\\s*\"next\"");
    207             if (linkPart.length() >= 1 && linkPart.charAt(0) == '<' && linkPart.endsWith(">")) {
    208               try {
    209                 url = new URL(linkPart.substring(1, linkPart.length() - 1));
    210               } catch (final MalformedURLException e) {
    211                 Logging.log(Logging.LEVEL_WARN,
    212                     "Mapillary API v3 returns a malformed URL in the Link header.", e);
    213               }
    214             }
    215           }
    216           // If both a URL and the rel=next attribute are present, return the URL. Otherwise null is returned
    217           if (url != null && isNext) {
    218             return url;
    219           }
    220         }
    221       }
    222       return null;
    223     }
    224 
    225     public static String queryString(final Bounds bounds) {
    226       if (bounds != null) {
    227         final Map<String, String> parts = new HashMap<>();
    228         parts.put("bbox", bounds.toBBox().toStringCSV(","));
    229         return StreetsideURL.queryString(parts);
    230       }
    231       return StreetsideURL.queryString(null);
    232     }
    233 
    234     public static String queryStreetsideString(final Bounds bounds) {
    235       if (bounds != null) {
    236         final Map<String, String> parts = new HashMap<>();
    237         parts.put("bbox", bounds.toBBox().toStringCSV(","));
    238         return StreetsideURL.queryStreetsideBoundsString(parts);
    239       }
    240       return StreetsideURL.queryStreetsideBoundsString(null);
    241     }
    242 
    243   }
    244 
    245   public static final class VirtualEarth {
    246     private static final String BASE_URL_PREFIX = "https://t.ssl.ak.tiles.virtualearth.net/tiles/hs";
    247     private static final String BASE_URL_SUFFIX = ".jpg?g=6528&n=z";
    248 
    249     private VirtualEarth() {
    250       // Private constructor to avoid instantiation
    251     }
    252 
    253     public static URL streetsideTile(final String id, boolean thumbnail) {
    254       StringBuilder modifiedId = new StringBuilder();
    255 
    256       if (thumbnail) {
    257         // pad thumbnail imagery with leading zeros
    258         if (id.length() < 16) {
    259           for (int i = 0; i < 16 - id.length(); i++) {
    260             modifiedId.append("0");
    261           }
    262         }
    263         modifiedId.append(id).append("01");
    264       } else {
    265           if (Boolean.TRUE.equals(StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get())) {
    266               // pad 16-tiled imagery with leading zeros
    267               if (id.length() < 20) {
    268                   for (int i = 0; i < 20 - id.length(); i++) {
    269                       modifiedId.append("0");
    270                   }
    271               }
    272           } else {
    273               // pad 4-tiled imagery with leading zeros
    274               if (id.length() < 19) {
    275                   for (int i = 0; i < 19 - id.length(); i++) {
    276                       modifiedId.append("0");
    277                   }
    278               }
    279           }
    280           modifiedId.append(id);
    281       }
    282       URL url = StreetsideURL
    283           .string2URL(VirtualEarth.BASE_URL_PREFIX + modifiedId + VirtualEarth.BASE_URL_SUFFIX);
    284       if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    285         LOGGER.log(Logging.LEVEL_DEBUG, MessageFormat.format("Tile task URL {0} invoked.", url));
    286       }
    287       return url;
    288     }
    289   }
    290 
    291   public static final class MainWebsite {
    292 
    293     private MainWebsite() {
    294       // Private constructor to avoid instantiation
    295     }
    296 
    297     /**
    298      * Gives you the URL for the online viewer of a specific Streetside image.
    299      *
    300      * @param id the id of the image to which you want to link
    301      * @return the URL of the online viewer for the image with the given image key
    302      * @throws IllegalArgumentException if the image key is <code>null</code>
    303      */
    304     public static URL browseImage(String id) {
    305       if (id == null) {
    306         throw new IllegalArgumentException("The image id may not be null!");
    307       }
    308 
    309       StringBuilder modifiedId = new StringBuilder();
    310 
    311       // pad thumbnail imagery with leading zeros
    312       if (id.length() < 16) {
    313         for (int i = 0; i < 16 - id.length(); i++) {
    314           modifiedId.append("0");
    315         }
    316       }
    317       modifiedId.append(id).append("01");
    318 
    319       return StreetsideURL.string2URL(MessageFormat.format("{0}{1}{2}", VirtualEarth.BASE_URL_PREFIX, modifiedId,
    320           VirtualEarth.BASE_URL_SUFFIX));
    321     }
    322 
    323     /**
    324      * Gives you the URL for the blur editor of the image with the given key.
    325      *
    326      * @param id the key of the image for which you want to open the blur editor
    327      * @return the URL of the blur editor
    328      * @throws IllegalArgumentException if the image key is <code>null</code>
    329      */
    330     public static URL streetsidePrivacyLink(final String id) {
    331       if (id == null) {
    332         throw new IllegalArgumentException("The image id must not be null!");
    333       }
    334       String urlEncodedId;
    335       try {
    336         urlEncodedId = URLEncoder.encode(id, StandardCharsets.UTF_8.name());
    337       } catch (final UnsupportedEncodingException e) {
    338         LOGGER.log(Logging.LEVEL_ERROR, I18n.tr("Unsupported encoding when URL encoding", e), e);
    339         urlEncodedId = id;
    340       }
    341       return StreetsideURL.string2URL(StreetsideURL.STREETSIDE_PRIVACY_URL, urlEncodedId);
    342     }
    343 
    344   }
     77            return new URI(builder.toString()).toURL();
     78        } catch (final IllegalArgumentException | URISyntaxException | MalformedURLException e) {
     79            LOGGER.log(Logging.LEVEL_ERROR, e,
     80                    () -> MessageFormat.format("The class ''{0}'' produces malformed URLs like ''{1}''!",
     81                            StreetsideURL.class.getName(), builder));
     82            return null;
     83        }
     84    }
     85
     86    /**
     87     * A class for the current Streetside API
     88     */
     89    public static final class APIv3 {
     90
     91        private APIv3() {
     92            // Private constructor to avoid instantiation
     93        }
     94
     95        /**
     96         * Search for images inside some bounds
     97         * @param bounds The bounds to search
     98         * @return The URL to get
     99         */
     100        public static URL searchStreetsideImages(Bounds bounds) {
     101            return StreetsideURL.string2URL(StreetsideURL.STREETSIDE_BASE_URL, APIv3.queryStreetsideString(bounds));
     102        }
     103
     104        /**
     105         * The APIv3 returns a Link header for each request. It contains a URL for requesting more results.
     106         * If you supply the value of the Link header, this method returns the next URL,
     107         * if such a URL is defined in the header.
     108         *
     109         * @param value the value of the HTTP-header with key "Link"
     110         * @return the {@link URL} for the next result page, or <code>null</code> if no such URL could be found
     111         */
     112        public static URL parseNextFromLinkHeaderValue(String value) {
     113            if (value != null) {
     114                // Iterate over the different entries of the Link header
     115                for (final String link : value.split(",", Integer.MAX_VALUE)) {
     116                    var isNext = false;
     117                    URL url = null;
     118                    // Iterate over the parts of each entry (typically it's one `rel="‹linkType›"` and one like `<https://URL>`)
     119                    for (String linkPart : link.split(";", Integer.MAX_VALUE)) {
     120                        linkPart = linkPart.trim();
     121                        isNext |= linkPart.matches("rel\\s*=\\s*\"next\"");
     122                        if (linkPart.length() >= 1 && linkPart.charAt(0) == '<' && linkPart.endsWith(">")) {
     123                            try {
     124                                url = new URI(linkPart.substring(1, linkPart.length() - 1)).toURL();
     125                            } catch (final URISyntaxException | MalformedURLException e) {
     126                                Logging.log(Logging.LEVEL_WARN,
     127                                        "Mapillary API v3 returns a malformed URL in the Link header.", e);
     128                            }
     129                        }
     130                    }
     131                    // If both a URL and the rel=next attribute are present, return the URL. Otherwise null is returned
     132                    if (url != null && isNext) {
     133                        return url;
     134                    }
     135                }
     136            }
     137            return null;
     138        }
     139
     140        /**
     141         * Query for images inside some bounds
     142         * @param bounds The bounds to query
     143         * @return The URL to GET
     144         */
     145        public static String queryStreetsideString(final Bounds bounds) {
     146            if (bounds != null) {
     147                final Map<String, String> parts = new HashMap<>();
     148                parts.put("bbox", bounds.toBBox().toStringCSV(","));
     149                return StreetsideURL.queryStreetsideBoundsString(parts);
     150            }
     151            return StreetsideURL.queryStreetsideBoundsString(null);
     152        }
     153
     154    }
     155
     156    /**
     157     * A class for Microsoft Streetside website pages
     158     */
     159    public static final class MainWebsite {
     160
     161        private MainWebsite() {
     162            // Private constructor to avoid instantiation
     163        }
     164
     165        /**
     166         * Gives you the URL for the online viewer of a specific Streetside image.
     167         *
     168         * @param image The image to show
     169         * @return the URL of the online viewer for the image with the given image key
     170         * @throws IllegalArgumentException if the image key is <code>null</code>
     171         */
     172        public static URL browseImage(ILatLon image) {
     173            if (image == null || !image.isLatLonKnown()) {
     174                throw new IllegalArgumentException("The image and image lat/lon may not be null!");
     175            }
     176            // The online viewer only takes lat/lon for args
     177            // lvl == zoom level (needs to be high enough for MS to show streetside imagery)
     178            // style=x -- needed to show the streetside imagery
     179            return StreetsideURL.string2URL("https://www.bing.com/maps?lvl=16&style=x&cp=",
     180                    Double.toString(image.lat()), "~", Double.toString(image.lon()));
     181        }
     182
     183        /**
     184         * Gives you the URL for the blur editor of the image with the given key.
     185         *
     186         * @param id the key of the image for which you want to open the blur editor
     187         * @return the URL of the blur editor
     188         * @throws IllegalArgumentException if the image key is <code>null</code>
     189         */
     190        public static URL streetsidePrivacyLink(final String id) {
     191            if (id == null) {
     192                throw new IllegalArgumentException("The image id must not be null!");
     193            }
     194            String urlEncodedId;
     195            urlEncodedId = URLEncoder.encode(id, StandardCharsets.UTF_8);
     196            return StreetsideURL.string2URL(StreetsideURL.STREETSIDE_PRIVACY_URL, urlEncodedId);
     197        }
     198
     199    }
    345200}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/StreetsideUtils.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside.utils;
    33
    4 import java.awt.Desktop;
    5 import java.io.IOException;
    6 import java.net.URISyntaxException;
    7 import java.net.URL;
    8 import java.text.ParseException;
    9 import java.text.SimpleDateFormat;
    10 import java.util.Calendar;
    11 import java.util.Locale;
    12 import java.util.Set;
    13 
    14 import javax.swing.SwingUtilities;
    15 
    16 import org.apache.commons.imaging.common.RationalNumber;
    17 import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants;
    18 import org.openstreetmap.josm.data.Bounds;
    19 import org.openstreetmap.josm.data.coor.LatLon;
    204import org.openstreetmap.josm.gui.MainApplication;
    21 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
    225import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
    23 import org.openstreetmap.josm.plugins.streetside.StreetsideSequence;
    246import org.openstreetmap.josm.tools.I18n;
    257
     
    3113public final class StreetsideUtils {
    3214
    33   private static final double MIN_ZOOM_SQUARE_SIDE = 0.002;
    34 
    35   private StreetsideUtils() {
    36     // Private constructor to avoid instantiation
    37   }
    38 
    39   /**
    40    * Open the default browser in the given URL.
    41    *
    42    * @param url The (not-null) URL that is going to be opened.
    43    * @throws IOException when the URL could not be opened
    44    */
    45   public static void browse(URL url) throws IOException {
    46     if (url == null) {
    47       throw new IllegalArgumentException();
    48     }
    49     Desktop desktop = Desktop.getDesktop();
    50     if (desktop.isSupported(Desktop.Action.BROWSE)) {
    51       try {
    52         desktop.browse(url.toURI());
    53       } catch (URISyntaxException e1) {
    54         throw new IOException(e1);
    55       }
    56     } else {
    57       Runtime runtime = Runtime.getRuntime();
    58       runtime.exec("xdg-open " + url);
    59     }
    60   }
    61 
    62   /**
    63    * Returns the current date formatted as EXIF timestamp.
    64    * As timezone the default timezone of the JVM is used ({@link java.util.TimeZone#getDefault()}).
    65    *
    66    * @return A {@code String} object containing the current date.
    67    */
    68   public static String currentDate() {
    69     return new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", Locale.UK).format(Calendar.getInstance().getTime());
    70   }
    71 
    72   /**
    73    * Returns current time in Epoch format (milliseconds since 1970-01-01T00:00:00+0000)
    74    *
    75    * @return The current date in Epoch format.
    76    */
    77   public static long currentTime() {
    78     return Calendar.getInstance().getTimeInMillis();
    79   }
    80 
    81   /**
    82    * Parses a string with a given format and returns the Epoch time.
    83    * If no timezone information is given, the default timezone of the JVM is used
    84    * ({@link java.util.TimeZone#getDefault()}).
    85    *
    86    * @param date   The string containing the date.
    87    * @param format The format of the date.
    88    * @return The date in Epoch format.
    89    * @throws ParseException if the date cannot be parsed with the given format
    90    */
    91   public static long getEpoch(String date, String format) throws ParseException {
    92     return new SimpleDateFormat(format, Locale.UK).parse(date).getTime();
    93   }
    94 
    95   /**
    96    * Calculates the decimal degree-value from a degree value given in
    97    * degrees-minutes-seconds-format
    98    *
    99    * @param degMinSec an array of length 3, the values in there are (in this order)
    100    *          degrees, minutes and seconds
    101    * @param ref     the latitude or longitude reference determining if the given value
    102    *          is:
    103    *          <ul>
    104    *          <li>north (
    105    *          {@link GpsTagConstants#GPS_TAG_GPS_LATITUDE_REF_VALUE_NORTH}) or
    106    *          south (
    107    *          {@link GpsTagConstants#GPS_TAG_GPS_LATITUDE_REF_VALUE_SOUTH}) of
    108    *          the equator</li>
    109    *          <li>east (
    110    *          {@link GpsTagConstants#GPS_TAG_GPS_LONGITUDE_REF_VALUE_EAST}) or
    111    *          west ({@link GpsTagConstants#GPS_TAG_GPS_LONGITUDE_REF_VALUE_WEST}
    112    *          ) of the equator</li>
    113    *          </ul>
    114    * @return the decimal degree-value for the given input, negative when west of
    115    * 0-meridian or south of equator, positive otherwise
    116    * @throws IllegalArgumentException if {@code degMinSec} doesn't have length 3 or if {@code ref} is
    117    *                  not one of the values mentioned above
    118    */
    119   public static double degMinSecToDouble(RationalNumber[] degMinSec, String ref) {
    120     if (degMinSec == null || degMinSec.length != 3) {
    121       throw new IllegalArgumentException("Array's length must be 3.");
    122     }
    123     for (int i = 0; i < 3; i++) {
    124       if (degMinSec[i] == null)
    125         throw new IllegalArgumentException("Null value in array.");
     15    private StreetsideUtils() {
     16        // Private constructor to avoid instantiation
    12617    }
    12718
    128     switch (ref) {
    129     case GpsTagConstants.GPS_TAG_GPS_LATITUDE_REF_VALUE_NORTH:
    130     case GpsTagConstants.GPS_TAG_GPS_LATITUDE_REF_VALUE_SOUTH:
    131     case GpsTagConstants.GPS_TAG_GPS_LONGITUDE_REF_VALUE_EAST:
    132     case GpsTagConstants.GPS_TAG_GPS_LONGITUDE_REF_VALUE_WEST:
    133       break;
    134     default:
    135       throw new IllegalArgumentException("Invalid ref.");
     19    /**
     20     * Updates the help text at the bottom of the window.
     21     */
     22    public static void updateHelpText() {
     23        if (MainApplication.getMap() == null || MainApplication.getMap().statusLine == null) {
     24            return;
     25        }
     26        final var ret = new StringBuilder();
     27        if (PluginState.isDownloading()) {
     28            ret.append(I18n.tr("Downloading Streetside images"));
     29        } else if (StreetsideLayer.hasInstance() && !StreetsideLayer.getInstance().getData().getImages().isEmpty()) {
     30            ret.append(I18n.tr("Total Streetside images: {0}", StreetsideLayer.getInstance().getToolTipText()));
     31        } else {
     32            ret.append(I18n.tr("No images found"));
     33        }
     34        if (StreetsideLayer.hasInstance() && StreetsideLayer.getInstance().mode != null) {
     35            ret.append(" — ").append(I18n.tr(StreetsideLayer.getInstance().mode.toString()));
     36        }
     37        MainApplication.getMap().statusLine.setHelpText(ret.toString());
    13638    }
    137 
    138     double result = degMinSec[0].doubleValue(); // degrees
    139     result += degMinSec[1].doubleValue() / 60; // minutes
    140     result += degMinSec[2].doubleValue() / 3600; // seconds
    141 
    142     if (GpsTagConstants.GPS_TAG_GPS_LATITUDE_REF_VALUE_SOUTH.equals(ref)
    143         || GpsTagConstants.GPS_TAG_GPS_LONGITUDE_REF_VALUE_WEST.equals(ref)) {
    144       result *= -1;
    145     }
    146 
    147     result = 360 * ((result + 180) / 360 - Math.floor((result + 180) / 360)) - 180;
    148     return result;
    149   }
    150 
    151   /**
    152    * Joins two images into the same sequence. One of them must be the last image of a sequence, the other one the beginning of a different one.
    153    *
    154    * @param imgA the first image, into whose sequence the images from the sequence of the second image are merged
    155    * @param imgB the second image, whose sequence is merged into the sequence of the first image
    156    */
    157   public static synchronized void join(StreetsideAbstractImage imgA, StreetsideAbstractImage imgB) {
    158     if (imgA == null || imgB == null) {
    159       throw new IllegalArgumentException("Both images must be non-null for joining.");
    160     }
    161     if (imgA.getSequence() == imgB.getSequence()) {
    162       throw new IllegalArgumentException("You can only join images of different sequences.");
    163     }
    164     if ((imgA.next() != null || imgB.previous() != null) && (imgB.next() != null || imgA.previous() != null)) {
    165       throw new IllegalArgumentException(
    166           "You can only join an image at the end of a sequence with one at the beginning of another sequence.");
    167     }
    168     if (imgA.next() != null || imgB.previous() != null) {
    169       join(imgB, imgA);
    170     } else {
    171       for (StreetsideAbstractImage img : imgB.getSequence().getImages()) {
    172         imgA.getSequence().add(img);
    173       }
    174       StreetsideLayer.invalidateInstance();
    175     }
    176   }
    177 
    178   /**
    179    * Zooms to fit all the {@link StreetsideAbstractImage} objects stored in the
    180    * database.
    181    */
    182   public static void showAllPictures() {
    183     showPictures(StreetsideLayer.getInstance().getData().getImages(), false);
    184   }
    185 
    186   /**
    187    * Zooms to fit all the given {@link StreetsideAbstractImage} objects.
    188    *
    189    * @param images The images your are zooming to.
    190    * @param select Whether the added images must be selected or not.
    191    */
    192   public static void showPictures(final Set<StreetsideAbstractImage> images, final boolean select) {
    193     if (!SwingUtilities.isEventDispatchThread()) {
    194       SwingUtilities.invokeLater(() -> showPictures(images, select));
    195     } else {
    196       Bounds zoomBounds;
    197       if (images.isEmpty()) {
    198         zoomBounds = new Bounds(new LatLon(0, 0));
    199       } else {
    200         zoomBounds = new Bounds(images.iterator().next().getMovingLatLon());
    201         for (StreetsideAbstractImage img : images) {
    202           zoomBounds.extend(img.getMovingLatLon());
    203         }
    204       }
    205 
    206       // The zoom rectangle must have a minimum size.
    207       double latExtent = Math.max(zoomBounds.getMaxLat() - zoomBounds.getMinLat(), MIN_ZOOM_SQUARE_SIDE);
    208       double lonExtent = Math.max(zoomBounds.getMaxLon() - zoomBounds.getMinLon(), MIN_ZOOM_SQUARE_SIDE);
    209       zoomBounds = new Bounds(zoomBounds.getCenter(), latExtent, lonExtent);
    210 
    211       MainApplication.getMap().mapView.zoomTo(zoomBounds);
    212       StreetsideLayer.getInstance().getData().setSelectedImage(null);
    213       if (select) {
    214         StreetsideLayer.getInstance().getData().addMultiSelectedImage(images);
    215       }
    216       StreetsideLayer.invalidateInstance();
    217     }
    218 
    219   }
    220 
    221   /**
    222    * Separates two images belonging to the same sequence. The two images have to be consecutive in the same sequence.
    223    * Two new sequences are created and all images up to (and including) either {@code imgA} or {@code imgB}
    224    * (whichever appears first in the sequence) are put into the first of the two sequences.
    225    * All others are put into the second new sequence.
    226    *
    227    * @param imgA one of the images marking where to split the sequence
    228    * @param imgB the other image marking where to split the sequence, needs to be a direct neighbour of {@code imgA} in the sequence.
    229    */
    230   public static synchronized void unjoin(StreetsideAbstractImage imgA, StreetsideAbstractImage imgB) {
    231     if (imgA == null || imgB == null) {
    232       throw new IllegalArgumentException("Both images must be non-null for unjoining.");
    233     }
    234     if (imgA.getSequence() != imgB.getSequence()) {
    235       throw new IllegalArgumentException("You can only unjoin with two images from the same sequence.");
    236     }
    237     if (imgB.equals(imgA.next()) && imgA.equals(imgB.next())) {
    238       throw new IllegalArgumentException(
    239           "When unjoining with two images these must be consecutive in one sequence.");
    240     }
    241 
    242     if (imgA.equals(imgB.next())) {
    243       unjoin(imgB, imgA);
    244     } else {
    245       StreetsideSequence seqA = new StreetsideSequence();
    246       StreetsideSequence seqB = new StreetsideSequence();
    247       boolean insideFirstHalf = true;
    248       for (StreetsideAbstractImage img : imgA.getSequence().getImages()) {
    249         if (insideFirstHalf) {
    250           seqA.add(img);
    251         } else {
    252           seqB.add(img);
    253         }
    254         if (img.equals(imgA)) {
    255           insideFirstHalf = false;
    256         }
    257       }
    258       StreetsideLayer.invalidateInstance();
    259     }
    260   }
    261 
    262   /**
    263    * Updates the help text at the bottom of the window.
    264    */
    265   public static void updateHelpText() {
    266     if (MainApplication.getMap() == null || MainApplication.getMap().statusLine == null) {
    267       return;
    268     }
    269     StringBuilder ret = new StringBuilder();
    270     if (PluginState.isDownloading()) {
    271       ret.append(I18n.tr("Downloading Streetside images"));
    272     } else if (StreetsideLayer.hasInstance() && !StreetsideLayer.getInstance().getData().getImages().isEmpty()) {
    273       ret.append(I18n.tr("Total Streetside images: {0}", StreetsideLayer.getInstance().getToolTipText()));
    274     } else if (PluginState.isSubmittingChangeset()) {
    275       ret.append(I18n.tr("Submitting Streetside Changeset"));
    276     } else {
    277       ret.append(I18n.tr("No images found"));
    278     }
    279     if (StreetsideLayer.hasInstance() && StreetsideLayer.getInstance().mode != null) {
    280       ret.append(" — ").append(I18n.tr(StreetsideLayer.getInstance().mode.toString()));
    281     }
    282     if (PluginState.isUploading()) {
    283       ret.append(" — ").append(PluginState.getUploadString());
    284     }
    285     MainApplication.getMap().statusLine.setHelpText(ret.toString());
    286   }
    28739}
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/StreetsideAbstractImageTest.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside;
    33
    4 import static org.junit.jupiter.api.Assertions.assertFalse;
    5 import static org.junit.jupiter.api.Assertions.assertTrue;
     4import static org.junit.jupiter.api.Assertions.assertAll;
     5import static org.junit.jupiter.api.Assertions.assertEquals;
    66
     7import java.text.MessageFormat;
     8import java.util.stream.Collectors;
     9import java.util.stream.Stream;
     10
     11import org.junit.jupiter.api.BeforeEach;
    712import org.junit.jupiter.api.Test;
    8 import org.openstreetmap.josm.data.coor.LatLon;
     13import org.junit.jupiter.params.ParameterizedTest;
     14import org.junit.jupiter.params.provider.Arguments;
     15import org.junit.jupiter.params.provider.MethodSource;
     16import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils;
     17import org.openstreetmap.josm.plugins.streetside.utils.TestUtil;
    918
    1019class StreetsideAbstractImageTest {
     20    private StreetsideImage image;
     21    private String imageUrlBase;
     22
     23    @BeforeEach
     24    void setup() {
     25        this.image = TestUtil.generateImage("1013203010232123", 39.065321, -108.553035);
     26        this.imageUrlBase = "https://ecn.t0.tiles.virtualearth.net/tiles/hs1013203010232123{0}?"
     27                + "g=14336&key=Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU";
     28    }
     29
    1130    @Test
    12     void testIsModified() {
    13         StreetsideImage img = new StreetsideImage("key___________________", new LatLon(0, 0), 0);
    14         assertFalse(img.isModified());
    15         img.turn(1e-4);
    16         img.stopMoving();
    17         assertTrue(img.isModified());
    18         img.turn(-1e-4);
    19         img.stopMoving();
    20         assertFalse(img.isModified());
    21         img.move(1e-4, 1e-4);
    22         img.stopMoving();
    23         assertTrue(img.isModified());
    24         img.move(-1e-4, -1e-4);
    25         img.stopMoving();
    26         assertFalse(img.isModified());
     31    void testThumbnail() {
     32        assertEquals(MessageFormat.format(this.imageUrlBase, "01"), this.image.getThumbnail());
     33    }
     34
     35    private static CubeMapTileXY frontCube(int x, int y) {
     36        return new CubeMapTileXY(CubemapUtils.CubemapFaces.FRONT, x, y);
     37    }
     38
     39    @Test
     40    void testTilesZoom1() {
     41        // 2x2 (4 total tiles)
     42        final var z1 = image.getFaceTiles(CubemapUtils.CubemapFaces.FRONT, 1)
     43                .collect(Collectors.toMap(p -> p.a, p -> p.b));
     44        assertEquals(MessageFormat.format(this.imageUrlBase, "010"), z1.get(frontCube(0, 0)));
     45        assertEquals(MessageFormat.format(this.imageUrlBase, "011"), z1.get(frontCube(1, 0)));
     46        assertEquals(MessageFormat.format(this.imageUrlBase, "012"), z1.get(frontCube(0, 1)));
     47        assertEquals(MessageFormat.format(this.imageUrlBase, "013"), z1.get(frontCube(1, 1)));
     48        assertEquals(4, z1.size());
     49    }
     50
     51    @Test
     52    void testTilesZoom2() {
     53        // 4x4 (16 total tiles)
     54        final var z2 = image.getFaceTiles(CubemapUtils.CubemapFaces.FRONT, 2)
     55                .collect(Collectors.toMap(p -> p.a, p -> p.b));
     56        assertEquals(MessageFormat.format(this.imageUrlBase, "0100"), z2.get(frontCube(0, 0)));
     57        assertEquals(MessageFormat.format(this.imageUrlBase, "0101"), z2.get(frontCube(1, 0)));
     58        assertEquals(MessageFormat.format(this.imageUrlBase, "0102"), z2.get(frontCube(0, 1)));
     59        assertEquals(MessageFormat.format(this.imageUrlBase, "0103"), z2.get(frontCube(1, 1)));
     60        assertEquals(MessageFormat.format(this.imageUrlBase, "0110"), z2.get(frontCube(2, 0)));
     61        assertEquals(MessageFormat.format(this.imageUrlBase, "0111"), z2.get(frontCube(3, 0)));
     62        assertEquals(MessageFormat.format(this.imageUrlBase, "0112"), z2.get(frontCube(2, 1)));
     63        assertEquals(MessageFormat.format(this.imageUrlBase, "0113"), z2.get(frontCube(3, 1)));
     64        assertEquals(MessageFormat.format(this.imageUrlBase, "0120"), z2.get(frontCube(0, 2)));
     65        assertEquals(MessageFormat.format(this.imageUrlBase, "0121"), z2.get(frontCube(1, 2)));
     66        assertEquals(MessageFormat.format(this.imageUrlBase, "0122"), z2.get(frontCube(0, 3)));
     67        assertEquals(MessageFormat.format(this.imageUrlBase, "0123"), z2.get(frontCube(1, 3)));
     68        assertEquals(MessageFormat.format(this.imageUrlBase, "0130"), z2.get(frontCube(2, 2)));
     69        assertEquals(MessageFormat.format(this.imageUrlBase, "0131"), z2.get(frontCube(3, 2)));
     70        assertEquals(MessageFormat.format(this.imageUrlBase, "0132"), z2.get(frontCube(2, 3)));
     71        assertEquals(MessageFormat.format(this.imageUrlBase, "0133"), z2.get(frontCube(3, 3)));
     72        assertEquals(16, z2.size());
     73    }
     74
     75    @Test
     76    void testTilesZoom3() {
     77        // 8x8 (64 total tiles)
     78        final var z3 = image.getFaceTiles(CubemapUtils.CubemapFaces.FRONT, 3)
     79                .collect(Collectors.toMap(p -> p.a, p -> p.b));
     80        // Just check the first row to keep test size smallish
     81        assertEquals(MessageFormat.format(this.imageUrlBase, "01000"), z3.get(frontCube(0, 0)));
     82        assertEquals(MessageFormat.format(this.imageUrlBase, "01001"), z3.get(frontCube(1, 0)));
     83        assertEquals(MessageFormat.format(this.imageUrlBase, "01010"), z3.get(frontCube(2, 0)));
     84        assertEquals(MessageFormat.format(this.imageUrlBase, "01011"), z3.get(frontCube(3, 0)));
     85        assertEquals(MessageFormat.format(this.imageUrlBase, "01100"), z3.get(frontCube(4, 0)));
     86        assertEquals(MessageFormat.format(this.imageUrlBase, "01101"), z3.get(frontCube(5, 0)));
     87        assertEquals(MessageFormat.format(this.imageUrlBase, "01110"), z3.get(frontCube(6, 0)));
     88        assertEquals(MessageFormat.format(this.imageUrlBase, "01111"), z3.get(frontCube(7, 0)));
     89        assertEquals(64, z3.size());
     90    }
     91
     92    static Stream<Arguments> testQuadKeyToXY() {
     93        final Stream.Builder<Arguments> builder = Stream.builder();
     94        // 2x2
     95        builder.add(Arguments.of("0", 0, 0));
     96        builder.add(Arguments.of("1", 1, 0));
     97        builder.add(Arguments.of("2", 0, 1));
     98        builder.add(Arguments.of("3", 1, 1));
     99        // 4x4
     100        builder.add(Arguments.of("00", 0, 0));
     101        builder.add(Arguments.of("01", 1, 0));
     102        builder.add(Arguments.of("02", 0, 1));
     103        builder.add(Arguments.of("03", 1, 1));
     104        builder.add(Arguments.of("10", 2, 0));
     105        builder.add(Arguments.of("11", 3, 0));
     106        builder.add(Arguments.of("12", 2, 1));
     107        builder.add(Arguments.of("13", 3, 1));
     108        builder.add(Arguments.of("20", 0, 2));
     109        builder.add(Arguments.of("21", 1, 2));
     110        builder.add(Arguments.of("22", 0, 3));
     111        builder.add(Arguments.of("23", 1, 3));
     112        builder.add(Arguments.of("30", 2, 2));
     113        builder.add(Arguments.of("31", 3, 2));
     114        builder.add(Arguments.of("32", 2, 3));
     115        builder.add(Arguments.of("33", 3, 3));
     116        return builder.build();
     117    }
     118
     119    @ParameterizedTest
     120    @MethodSource
     121    void testQuadKeyToXY(String quadKey, int x, int y) {
     122        final var xy = StreetsideAbstractImage.quadKeyToTile(quadKey);
     123        assertAll(() -> assertEquals(x, xy.getXIndex(), "x"), () -> assertEquals(y, xy.getYIndex(), "y"));
    27124    }
    28125}
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/StreetsideDataTest.java

    r36194 r36228  
    1212import org.junit.jupiter.api.Disabled;
    1313import org.junit.jupiter.api.Test;
    14 import org.openstreetmap.josm.data.coor.LatLon;
     14import org.openstreetmap.josm.plugins.streetside.utils.TestUtil;
    1515import org.openstreetmap.josm.testutils.annotations.Main;
    1616
     
    3131
    3232    /**
    33      * Creates a sample {@link StreetsideData} objects, 4 {@link StreetsideImage}
    34      * objects and a {@link StreetsideSequence} object.
     33     * Creates a sample {@link StreetsideData} object and 4 {@link StreetsideImage}
     34     * objects.
    3535     */
    3636    @BeforeEach
    3737    public void setUp() {
    38         img1 = new StreetsideImage("id1__________________", new LatLon(0.1, 0.1), 90);
    39         img2 = new StreetsideImage("id2__________________", new LatLon(0.2, 0.2), 90);
    40         img3 = new StreetsideImage("id3__________________", new LatLon(0.3, 0.3), 90);
    41         img4 = new StreetsideImage("id4__________________", new LatLon(0.4, 0.4), 90);
    42         final StreetsideSequence seq = new StreetsideSequence();
    43 
    44         seq.add(Arrays.asList(img1, img2, img3, img4));
     38        img1 = TestUtil.generateImage("1", 0.1, 0.1);
     39        img2 = TestUtil.generateImage("2", 0.2, 0.2);
     40        img3 = TestUtil.generateImage("3", 0.3, 0.3);
     41        img4 = TestUtil.generateImage("4", 0.4, 0.4);
    4542
    4643        data = new StreetsideData();
    47         data.addAll(new ConcurrentSkipListSet<>(seq.getImages()));
     44        data.addAll(Arrays.asList(img1, img2, img3, img4));
    4845    }
    4946
     
    7269    void testSize() {
    7370        assertEquals(4, data.getImages().size());
    74         data.add(new StreetsideImage("id5__________________", new LatLon(0.1, 0.1), 90));
     71        data.add(TestUtil.generateImage("5", 0.1, 0.1));
    7572        assertEquals(5, data.getImages().size());
    7673    }
    7774
    7875    /**
    79      * Test the {@link StreetsideData#setHighlightedImage(StreetsideAbstractImage)}
     76     * Test the {@link StreetsideData#setHighlightedImage(StreetsideImage)}
    8077     * and {@link StreetsideData#getHighlightedImage()} methods.
    8178     */
     
    137134        assertThrows(IllegalStateException.class, data::selectPrevious);
    138135    }
    139 
    140     /**
    141      * Test the multiselection of images. When a new image is selected, the
    142      * multiselected List should reset.
    143      */
    144     @Disabled("The imgs have non-int identifiers while the code expects the identifiers to be int in string form")
    145     @Test
    146     void testMultiSelect() {
    147         assertEquals(0, data.getMultiSelectedImages().size());
    148         data.setSelectedImage(img1);
    149         assertEquals(1, data.getMultiSelectedImages().size());
    150         data.addMultiSelectedImage(img2);
    151         assertEquals(2, data.getMultiSelectedImages().size());
    152         data.setSelectedImage(img1);
    153         assertEquals(1, data.getMultiSelectedImages().size());
    154     }
    155136}
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/StreetsideLayerTest.java

    r36194 r36228  
    1010import org.junit.jupiter.api.Test;
    1111import org.junit.jupiter.api.condition.DisabledIf;
    12 import org.openstreetmap.josm.data.coor.LatLon;
    1312import org.openstreetmap.josm.data.imagery.ImageryInfo;
    1413import org.openstreetmap.josm.gui.layer.ImageryLayer;
    1514import org.openstreetmap.josm.gui.layer.Layer;
    16 import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils;
    1715import org.openstreetmap.josm.testutils.JOSMTestRules;
    1816import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
     
    4644
    4745    @Test
    48     void testSetVisible() {
    49         StreetsideLayer.getInstance().getData()
    50                 .add(new StreetsideImage(CubemapUtils.TEST_IMAGE_ID, new LatLon(0.0, 0.0), 0.0));
    51         StreetsideLayer.getInstance().getData()
    52                 .add(new StreetsideImage(CubemapUtils.TEST_IMAGE_ID, new LatLon(0.0, 0.0), 0.0));
    53         StreetsideImage invisibleImage = new StreetsideImage(CubemapUtils.TEST_IMAGE_ID, new LatLon(0.0, 0.0), 0.0);
    54         invisibleImage.setVisible(false);
    55         StreetsideLayer.getInstance().getData().add(invisibleImage);
    56 
    57         StreetsideLayer.getInstance().setVisible(false);
    58         for (StreetsideAbstractImage img : StreetsideLayer.getInstance().getData().getImages()) {
    59             assertFalse(img.isVisible());
    60         }
    61 
    62         StreetsideLayer.getInstance().setVisible(true);
    63         for (StreetsideAbstractImage img : StreetsideLayer.getInstance().getData().getImages()) {
    64             assertTrue(img.isVisible());
    65         }
    66     }
    67 
    68     @Test
    6946    void testGetInfoComponent() {
    7047        Object comp = StreetsideLayer.getInstance().getInfoComponent();
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/cache/StreetsideCacheTest.java

    r36194 r36228  
    44import static org.junit.jupiter.api.Assertions.assertFalse;
    55import static org.junit.jupiter.api.Assertions.assertNotNull;
    6 import static org.junit.jupiter.api.Assertions.assertNull;
    76
    87import org.junit.jupiter.api.Test;
    9 import org.openstreetmap.josm.plugins.streetside.cache.StreetsideCache.Type;
    108import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
    119
     
    1513    @Test
    1614    void testCache() {
    17         StreetsideCache cache = new StreetsideCache("00000", Type.FULL_IMAGE);
     15        StreetsideCache cache = new StreetsideCache("https://ecn.t0.tiles.virtualearth.net/tiles/hs101320223333223201");
    1816        assertNotNull(cache.getUrl());
    1917        assertNotNull(cache.getCacheKey());
     
    2119        assertFalse(cache.isObjectLoadable());
    2220
    23         cache = new StreetsideCache("00000", Type.THUMBNAIL);
     21        cache = new StreetsideCache("https://ecn.t0.tiles.virtualearth.net/tiles/hs101320223333223201");
    2422        assertNotNull(cache.getCacheKey());
    2523        assertNotNull(cache.getUrl());
    26 
    27         cache = new StreetsideCache(null, null);
    28         assertNull(cache.getCacheKey());
    29         assertNull(cache.getUrl());
    3024    }
    3125}
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/cubemap/CubemapUtilsTest.java

    r36194 r36228  
    44import static org.junit.jupiter.api.Assertions.assertEquals;
    55
    6 import org.junit.jupiter.api.Disabled;
    76import org.junit.jupiter.api.Test;
    87
     
    2827        assertEquals("680931568", res);
    2928    }
    30 
    31     @Disabled
    32     @Test
    33     void testGetFaceNumberForCount() {
    34         String faceNrFront = CubemapUtils.getFaceNumberForCount(0);
    35         String faceNrRight = CubemapUtils.getFaceNumberForCount(1);
    36         String faceNrBack = CubemapUtils.getFaceNumberForCount(2);
    37         String faceNrLeft = CubemapUtils.getFaceNumberForCount(3);
    38         String faceNrUp = CubemapUtils.getFaceNumberForCount(4);
    39         String faceNrDown = CubemapUtils.getFaceNumberForCount(5);
    40         assertEquals(faceNrFront, "01");
    41         assertEquals(faceNrRight, "02");
    42         assertEquals(faceNrBack, "03");
    43         assertEquals(faceNrLeft, "10");
    44         assertEquals(faceNrUp, "11");
    45         assertEquals(faceNrDown, "12");
    46     }
    47 
    48     @Disabled
    49     @Test
    50     void testGetCount4FaceNumber() {
    51         int count4Front = CubemapUtils.getCount4FaceNumber("01");
    52         int count4Right = CubemapUtils.getCount4FaceNumber("02");
    53         int count4Back = CubemapUtils.getCount4FaceNumber("03");
    54         int count4Left = CubemapUtils.getCount4FaceNumber("10");
    55         int count4Up = CubemapUtils.getCount4FaceNumber("11");
    56         int count4Down = CubemapUtils.getCount4FaceNumber("12");
    57         assertEquals(count4Front, 0);
    58         assertEquals(count4Right, 1);
    59         assertEquals(count4Back, 2);
    60         assertEquals(count4Left, 3);
    61         assertEquals(count4Up, 4);
    62         assertEquals(count4Down, 5);
    63     }
    64 
    65     @Test
    66     void testConvertDoubleCountNrto16TileNr() {
    67         String x0y0 = CubemapUtils.convertDoubleCountNrto16TileNr("00");
    68         String x0y1 = CubemapUtils.convertDoubleCountNrto16TileNr("01");
    69         String x0y2 = CubemapUtils.convertDoubleCountNrto16TileNr("02");
    70         String x0y3 = CubemapUtils.convertDoubleCountNrto16TileNr("03");
    71         String x1y0 = CubemapUtils.convertDoubleCountNrto16TileNr("10");
    72         String x1y1 = CubemapUtils.convertDoubleCountNrto16TileNr("11");
    73         String x1y2 = CubemapUtils.convertDoubleCountNrto16TileNr("12");
    74         String x1y3 = CubemapUtils.convertDoubleCountNrto16TileNr("13");
    75         String x2y0 = CubemapUtils.convertDoubleCountNrto16TileNr("20");
    76         String x2y1 = CubemapUtils.convertDoubleCountNrto16TileNr("21");
    77         String x2y2 = CubemapUtils.convertDoubleCountNrto16TileNr("22");
    78         String x2y3 = CubemapUtils.convertDoubleCountNrto16TileNr("23");
    79         String x3y0 = CubemapUtils.convertDoubleCountNrto16TileNr("30");
    80         String x3y1 = CubemapUtils.convertDoubleCountNrto16TileNr("31");
    81         String x3y2 = CubemapUtils.convertDoubleCountNrto16TileNr("32");
    82         String x3y3 = CubemapUtils.convertDoubleCountNrto16TileNr("33");
    83 
    84         assertEquals(x0y0, "00");
    85         assertEquals(x0y1, "01");
    86         assertEquals(x0y2, "10");
    87         assertEquals(x0y3, "11");
    88         assertEquals(x1y0, "02");
    89         assertEquals(x1y1, "03");
    90         assertEquals(x1y2, "12");
    91         assertEquals(x1y3, "13");
    92         assertEquals(x2y0, "20");
    93         assertEquals(x2y1, "21");
    94         assertEquals(x2y2, "30");
    95         assertEquals(x2y3, "31");
    96         assertEquals(x3y0, "22");
    97         assertEquals(x3y1, "23");
    98         assertEquals(x3y2, "32");
    99         assertEquals(x3y3, "33");
    100     }
    10129}
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/cubemap/TileDownloadingTaskTest.java

    r36194 r36228  
    77import java.util.List;
    88import java.util.concurrent.Callable;
     9import java.util.concurrent.ExecutionException;
    910import java.util.concurrent.ExecutorService;
    1011import java.util.concurrent.Executors;
     
    1314import org.junit.jupiter.api.Disabled;
    1415import org.junit.jupiter.api.Test;
     16import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY;
     17import org.openstreetmap.josm.plugins.streetside.utils.TestUtil;
    1518
    1619@Disabled
     
    1821
    1922    @Test
    20     final void testCall() throws InterruptedException {
    21         ExecutorService pool = Executors.newFixedThreadPool(1);
    22         List<Callable<List<String>>> tasks = new ArrayList<>(1);
    23         tasks.add(new TileDownloadingTask("2202112030033001233"));
    24         List<Future<List<String>>> results = pool.invokeAll(tasks);
    25         assertEquals(results.get(0), "2202112030033001233");
     23    final void testCall() throws InterruptedException, ExecutionException {
     24        try (ExecutorService pool = Executors.newFixedThreadPool(1)) {
     25            List<Callable<List<String>>> tasks = new ArrayList<>(1);
     26            tasks.add(new TileDownloadingTask(TestUtil.generateImage("2202112030033001233", 0, 0),
     27                    CubemapUtils.CubemapFaces.FRONT, new CubeMapTileXY(CubemapUtils.CubemapFaces.FRONT, 0, 0)));
     28            List<Future<List<String>>> results = pool.invokeAll(tasks);
     29            assertEquals("2202112030033001233", results.get(0).get().get(0));
     30        }
    2631    }
    2732}
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/gui/ImageDisplayTest.java

    r36194 r36228  
    2525    void testImagePersistence() {
    2626        StreetsideImageDisplay display = new StreetsideImageDisplay();
    27         display.setImage(DUMMY_IMAGE, null);
     27        display.setImage(DUMMY_IMAGE);
    2828        assertEquals(DUMMY_IMAGE, display.getImage());
    2929    }
     
    4444        display.getMouseWheelListeners()[0].mouseWheelMoved(dummyScroll);
    4545
    46         display.setImage(DUMMY_IMAGE, null);
     46        display.setImage(DUMMY_IMAGE);
    4747
    4848        display.getMouseWheelListeners()[0].mouseWheelMoved(dummyScroll);
     
    7272            display.getMouseListeners()[0].mouseClicked(dummyClick);
    7373
    74             display.setImage(DUMMY_IMAGE, null);
     74            display.setImage(DUMMY_IMAGE);
    7575
    7676            display.getMouseListeners()[0].mouseClicked(dummyClick);
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/gui/StreetsidePreferenceSettingTest.java

    r36194 r36228  
    66
    77import java.awt.GraphicsEnvironment;
     8import java.util.Objects;
    89
    910import javax.swing.JCheckBox;
     
    99100            settings.ok();
    100101            assertEquals(new StringProperty("streetside.download-mode", "default").get(),
    101                     DOWNLOAD_MODE.fromLabel(((JComboBox<String>) getPrivateFieldValue(settings, "downloadModeComboBox"))
    102                             .getSelectedItem().toString()).getPrefId());
     102                    DOWNLOAD_MODE.fromLabel(Objects.requireNonNull(((JComboBox<String>) getPrivateFieldValue(settings, "downloadModeComboBox"))
     103                            .getSelectedItem()).toString()).getPrefId());
    103104        }
    104105    }
    105106
    106107    /**
    107      * Checks, if a certain {@link BooleanProperty} (identified by the {@code propName} attribute) matches the selected-state of the given {@link JCheckBox}
     108     * Checks, if a certain {@link BooleanProperty} (identified by the {@code propName} attribute)
     109     * matches the selected-state of the given {@link JCheckBox}
    108110     * @param cb the {@link JCheckBox}, which should be checked against the {@link BooleanProperty}
    109111     * @param propName the name of the property against which the selected-state of the given {@link JCheckBox} should be checked
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/io/download/SequenceDownloadRunnableTest.java

    r36194 r36228  
    33
    44import static org.junit.jupiter.api.Assertions.assertEquals;
    5 
    6 import java.lang.reflect.Field;
    7 import java.net.MalformedURLException;
    8 import java.net.URL;
    9 import java.util.function.Function;
    105
    116import org.junit.jupiter.api.Disabled;
     
    2015class SequenceDownloadRunnableTest {
    2116
    22     private static final Function<Bounds, URL> SEARCH_SEQUENCES_URL_GEN = b -> {
    23         return SequenceDownloadRunnableTest.class.getResource("/api/v3/responses/searchSequences.json");
    24     };
    25     private Field urlGenField;
    26 
    2717    @Test
    28     void testRun1() throws IllegalArgumentException, IllegalAccessException {
    29         testNumberOfDecodedImages(4, SEARCH_SEQUENCES_URL_GEN, new Bounds(7.246497, 16.432955, 7.249027, 16.432976));
     18    void testRun1() throws IllegalArgumentException {
     19        testNumberOfDecodedImages(4, new Bounds(7.246497, 16.432955, 7.249027, 16.432976));
    3020    }
    3121
    3222    @Test
    33     void testRun2() throws IllegalArgumentException, IllegalAccessException {
    34         testNumberOfDecodedImages(0, SEARCH_SEQUENCES_URL_GEN, new Bounds(0, 0, 0, 0));
     23    void testRun2() throws IllegalArgumentException {
     24        testNumberOfDecodedImages(0, new Bounds(0, 0, 0, 0));
    3525    }
    3626
    3727    @Test
    38     void testRun3() throws IllegalArgumentException, IllegalAccessException {
    39         testNumberOfDecodedImages(0, b -> {
    40             try {
    41                 return new URL("https://streetside/nonexistentURL");
    42             } catch (MalformedURLException e) {
    43                 return null;
    44             }
    45         }, new Bounds(0, 0, 0, 0));
     28    void testRun3() throws IllegalArgumentException {
     29        testNumberOfDecodedImages(0, new Bounds(0, 0, 0, 0));
    4630    }
    4731
    4832    @Test
    49     void testRun4() throws IllegalArgumentException, IllegalAccessException {
     33    void testRun4() throws IllegalArgumentException {
    5034        StreetsideProperties.CUT_OFF_SEQUENCES_AT_BOUNDS.put(true);
    51         testNumberOfDecodedImages(4, SEARCH_SEQUENCES_URL_GEN, new Bounds(7.246497, 16.432955, 7.249027, 16.432976));
     35        testNumberOfDecodedImages(4, new Bounds(7.246497, 16.432955, 7.249027, 16.432976));
    5236    }
    5337
    5438    @Test
    55     void testRun5() throws IllegalArgumentException, IllegalAccessException {
     39    void testRun5() throws IllegalArgumentException {
    5640        StreetsideProperties.CUT_OFF_SEQUENCES_AT_BOUNDS.put(true);
    57         testNumberOfDecodedImages(0, SEARCH_SEQUENCES_URL_GEN, new Bounds(0, 0, 0, 0));
     41        testNumberOfDecodedImages(0, new Bounds(0, 0, 0, 0));
    5842    }
    5943
    60     private void testNumberOfDecodedImages(int expectedNumImgs, Function<Bounds, URL> urlGen, Bounds bounds)
    61             throws IllegalArgumentException, IllegalAccessException {
     44    private void testNumberOfDecodedImages(int expectedNumImgs, Bounds bounds)
     45            throws IllegalArgumentException {
    6246        SequenceDownloadRunnable r = new SequenceDownloadRunnable(StreetsideLayer.getInstance().getData(), bounds);
    63         urlGenField.set(null, urlGen);
    6447        r.run();
    6548        assertEquals(expectedNumImgs, StreetsideLayer.getInstance().getData().getImages().size());
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/utils/PluginStateTest.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside.utils;
    33
    4 import static org.junit.jupiter.api.Assertions.assertEquals;
    54import static org.junit.jupiter.api.Assertions.assertFalse;
    65import static org.junit.jupiter.api.Assertions.assertTrue;
    76
    8 import javax.swing.JOptionPane;
    9 
    107import org.junit.jupiter.api.Test;
    11 import org.openstreetmap.josm.TestUtils;
    12 import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker;
    138
    149/**
     
    3530        assertFalse(PluginState.isDownloading());
    3631    }
    37 
    38     /**
    39      * Tests the methods related to the upload.
    40      */
    41     @Test
    42     void testUpload() {
    43         TestUtils.assumeWorkingJMockit();
    44         JOptionPaneSimpleMocker jopsMocker = new JOptionPaneSimpleMocker();
    45         jopsMocker.getMockResultMap().put("You have successfully uploaded 2 images to Bing.com", JOptionPane.OK_OPTION);
    46         assertFalse(PluginState.isUploading());
    47         PluginState.addImagesToUpload(2);
    48         assertEquals(2, PluginState.getImagesToUpload());
    49         assertEquals(0, PluginState.getImagesUploaded());
    50         assertTrue(PluginState.isUploading());
    51         PluginState.imageUploaded();
    52         assertEquals(1, PluginState.getImagesUploaded());
    53         assertTrue(PluginState.isUploading());
    54         PluginState.imageUploaded();
    55         assertFalse(PluginState.isUploading());
    56         assertEquals(2, PluginState.getImagesToUpload());
    57         assertEquals(2, PluginState.getImagesUploaded());
    58     }
    5932}
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/utils/StreetsideURLTest.java

    r36194 r36228  
    55import static org.junit.jupiter.api.Assertions.assertNull;
    66import static org.junit.jupiter.api.Assertions.assertThrows;
     7import static org.junit.jupiter.api.Assertions.fail;
    78
    89import java.lang.reflect.InvocationTargetException;
    910import java.lang.reflect.Method;
    1011import java.net.MalformedURLException;
    11 import java.net.URL;
     12import java.net.URI;
     13import java.net.URISyntaxException;
    1214
    1315import org.junit.jupiter.api.Assertions;
     
    1618
    1719class StreetsideURLTest {
    18     // TODO: replace with Streetside URL @rrh
    19     private static final String CLIENT_ID_QUERY_PART = "client_id=T1Fzd20xZjdtR0s1VDk5OFNIOXpYdzoxNDYyOGRkYzUyYTFiMzgz";
    20 
    21     public static class APIv3 {
    22 
    23         /*@Ignore
    24         @Test
    25         public void testSearchDetections() {
    26           assertUrlEquals(StreetsideURL.APIv3.searchDetections(null), "https://a.streetside.com/v3/detections", CLIENT_ID_QUERY_PART);
    27         }
    28 
    29         @Ignore
    30         @Test
    31         public void testSearchImages() {
    32           assertUrlEquals(StreetsideURL.APIv3.searchImages(null), "https://a.streetside.com/v3/images", CLIENT_ID_QUERY_PART);
    33         }
    34 
    35         @Ignore
    36         @Test
    37         public void testSubmitChangeset() throws MalformedURLException {
    38           assertEquals(
    39         new URL("https://a.streetside.com/v3/changesets?" + CLIENT_ID_QUERY_PART),
    40         StreetsideURL.APIv3.submitChangeset()
    41           );
    42         }*/
     20    @Test
     21    void testParseNextFromHeaderValue() throws MalformedURLException {
     22        String headerVal = "<https://a.streetside.com/v3/sequences?page=1&per_page=200"
     23                + "&client_id=TG1sUUxGQlBiYWx2V05NM0pQNUVMQTo2NTU3NTBiNTk1NzM1Y2U2>; rel=\"first\", "
     24                + "<https://a.streetside.com/v3/sequences?page=2&per_page=200"
     25                + "&client_id=TG1sUUxGQlBiYWx2V05NM0pQNUVMQTo2NTU3NTBiNTk1NzM1Y2U2>; rel=\"prev\", "
     26                + "<https://a.streetside.com/v3/sequences?page=4&per_page=200"
     27                + "&client_id=TG1sUUxGQlBiYWx2V05NM0pQNUVMQTo2NTU3NTBiNTk1NzM1Y2U2>; rel=\"next\"";
     28        assertEquals(URI.create(
     29                "https://a.streetside.com/v3/sequences?page=4&per_page=200&client_id=TG1sUUxGQlBiYWx2V05NM0pQNUVMQTo2NTU3NTBiNTk1NzM1Y2U2")
     30                .toURL(), StreetsideURL.APIv3.parseNextFromLinkHeaderValue(headerVal));
    4331    }
    4432
    4533    @Test
    46     void testParseNextFromHeaderValue() throws MalformedURLException {
    47         String headerVal = "<https://a.streetside.com/v3/sequences?page=1&per_page=200&client_id=TG1sUUxGQlBiYWx2V05NM0pQNUVMQTo2NTU3NTBiNTk1NzM1Y2U2>; rel=\"first\", "
    48                 + "<https://a.streetside.com/v3/sequences?page=2&per_page=200&client_id=TG1sUUxGQlBiYWx2V05NM0pQNUVMQTo2NTU3NTBiNTk1NzM1Y2U2>; rel=\"prev\", "
    49                 + "<https://a.streetside.com/v3/sequences?page=4&per_page=200&client_id=TG1sUUxGQlBiYWx2V05NM0pQNUVMQTo2NTU3NTBiNTk1NzM1Y2U2>; rel=\"next\"";
    50         assertEquals(new URL(
    51                 "https://a.streetside.com/v3/sequences?page=4&per_page=200&client_id=TG1sUUxGQlBiYWx2V05NM0pQNUVMQTo2NTU3NTBiNTk1NzM1Y2U2"),
    52                 StreetsideURL.APIv3.parseNextFromLinkHeaderValue(headerVal));
    53     }
    54 
    55     @Test
    56     void testParseNextFromHeaderValue2() throws MalformedURLException {
     34    void testParseNextFromHeaderValue2() throws MalformedURLException, URISyntaxException {
    5735        String headerVal = "<https://urlFirst>; rel=\"first\", " + "rel = \"next\" ; < ; , "
    5836                + "rel = \"next\" ; <https://urlNext> , " + "<https://urlPrev>; rel=\"prev\"";
    59         assertEquals(new URL("https://urlNext"), StreetsideURL.APIv3.parseNextFromLinkHeaderValue(headerVal));
     37        assertEquals(new URI("https://urlNext").toURL(), StreetsideURL.APIv3.parseNextFromLinkHeaderValue(headerVal));
    6038    }
    6139
     
    7048    }
    7149
    72     /*public static class Cloudfront {
    73     @Ignore
    74     @Test
    75     public void testThumbnail() {
    76       assertUrlEquals(StreetsideURL.VirtualEarth.streetsideTile("arbitrary_key", true), "https://d1cuyjsrcm0gby.cloudfront.net/arbitrary_key/thumb-2048.jpg");
    77       assertUrlEquals(StreetsideURL.VirtualEarth.streetsideTile("arbitrary_key2", false), "https://d1cuyjsrcm0gby.cloudfront.net/arbitrary_key2/thumb-320.jpg");
    78     }
    79     }*/
    80 
    8150    @Disabled
    8251    @Test
    8352    void testBrowseImageURL() throws MalformedURLException {
    84         assertEquals(new URL("https://www.streetside.com/map/im/1234567890123456789012"),
    85                 StreetsideURL.MainWebsite.browseImage("1234567890123456789012"));
     53        fail("Needs editing for MS Streetside");
     54        // assertEquals(new URL("https://www.streetside.com/map/im/1234567890123456789012"),
     55        // StreetsideURL.MainWebsite.browseImage("1234567890123456789012"));
    8656    }
    8757
     
    9161    }
    9262
    93     @Disabled
    94     @Test
    95     void testConnectURL() {
    96         /*assertUrlEquals(
    97         StreetsideURL.MainWebsite.connect("http://redirect-host/ä"),
    98         "https://www.streetside.com/connect",
    99         CLIENT_ID_QUERY_PART,
    100         "scope=user%3Aread+public%3Aupload+public%3Awrite",
    101         "response_type=token",
    102         "redirect_uri=http%3A%2F%2Fredirect-host%2F%C3%A4"
    103         );
    104 
    105         assertUrlEquals(
    106         StreetsideURL.MainWebsite.connect(null),
    107         "https://www.streetside.com/connect",
    108         CLIENT_ID_QUERY_PART,
    109         "scope=user%3Aread+public%3Aupload+public%3Awrite",
    110         "response_type=token"
    111         );
    112 
    113         assertUrlEquals(
    114         StreetsideURL.MainWebsite.connect(""),
    115         "https://www.streetside.com/connect",
    116         CLIENT_ID_QUERY_PART,
    117         "scope=user%3Aread+public%3Aupload+public%3Awrite",
    118         "response_type=token"
    119         );*/
    120     }
    121 
    122     @Disabled
    123     @Test
    124     void testUploadSecretsURL() throws MalformedURLException {
    125         /*assertEquals(
    126         new URL("https://a.streetside.com/v2/me/uploads/secrets?"+CLIENT_ID_QUERY_PART),
    127         StreetsideURL.uploadSecretsURL()
    128         );*/
    129     }
    130 
    131     @Disabled
    132     @Test
    133     void testUserURL() throws MalformedURLException {
    134         /*assertEquals(
    135         new URL("https://a.streetside.com/v3/me?"+CLIENT_ID_QUERY_PART),
    136         StreetsideURL.APIv3.userURL()
    137         );*/
    138     }
    139 
    14063    @Test
    14164    void testString2MalformedURL() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
     
    14366        Method method = StreetsideURL.class.getDeclaredMethod("string2URL", String[].class);
    14467        method.setAccessible(true);
    145         Assertions.assertNull(method.invoke(null, new Object[] { new String[] { "malformed URL" } })); // this simply invokes string2URL("malformed URL")
    146         Assertions.assertNull(method.invoke(null, new Object[] { null })); // invokes string2URL(null)
     68        // this simply invokes string2URL("malformed URL")
     69        Assertions.assertNull(method.invoke(null, (Object) new String[] { "malformed URL" }));
     70        // invokes string2URL(null)
     71        Assertions.assertNull(method.invoke(null, (Object) null));
    14772    }
    14873
     
    15176        TestUtil.testUtilityClass(StreetsideURL.class);
    15277        TestUtil.testUtilityClass(StreetsideURL.APIv3.class);
    153         TestUtil.testUtilityClass(StreetsideURL.VirtualEarth.class);
    15478        TestUtil.testUtilityClass(StreetsideURL.MainWebsite.class);
    15579    }
    156 
    157     private static void assertUrlEquals(URL actualUrl, String expectedBaseUrl, String... expectedParams) {
    158         final String actualUrlString = actualUrl.toString();
    159         assertEquals(expectedBaseUrl,
    160                 actualUrlString.contains("?") ? actualUrlString.substring(0, actualUrlString.indexOf('?'))
    161                         : actualUrlString);
    162         String[] actualParams = actualUrl.getQuery() == null ? new String[0] : actualUrl.getQuery().split("&");
    163         assertEquals(expectedParams.length, actualParams.length);
    164         for (int exIndex = 0; exIndex < expectedParams.length; exIndex++) {
    165             boolean parameterIsPresent = false;
    166             for (int acIndex = 0; !parameterIsPresent && acIndex < actualParams.length; acIndex++) {
    167                 parameterIsPresent |= actualParams[acIndex].equals(expectedParams[exIndex]);
    168             }
    169             Assertions.assertTrue(parameterIsPresent, expectedParams[exIndex] + " was expected in the query string of "
    170                     + actualUrl + " but wasn't there.");
    171         }
    172     }
    17380}
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/utils/StreetsideUtilsTest.java

    r36194 r36228  
    22package org.openstreetmap.josm.plugins.streetside.utils;
    33
    4 import static org.junit.jupiter.api.Assertions.assertEquals;
    5 
    6 import org.apache.commons.imaging.common.RationalNumber;
    7 import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants;
    84import org.junit.jupiter.api.Test;
    95
     
    2117    }
    2218
    23     /**
    24      * Test {@link StreetsideUtils#degMinSecToDouble(RationalNumber[], String)}
    25      * method.
    26      */
    27     @Test
    28     void testDegMinSecToDouble() {
    29         RationalNumber[] num = new RationalNumber[3];
    30         num[0] = new RationalNumber(1, 1);
    31         num[1] = new RationalNumber(0, 1);
    32         num[2] = new RationalNumber(0, 1);
    33         String ref = GpsTagConstants.GPS_TAG_GPS_LATITUDE_REF_VALUE_NORTH;
    34         assertEquals(1, StreetsideUtils.degMinSecToDouble(num, ref), 0.01);
    35         ref = GpsTagConstants.GPS_TAG_GPS_LATITUDE_REF_VALUE_SOUTH;
    36         assertEquals(-1, StreetsideUtils.degMinSecToDouble(num, ref), 0.01);
    37         num[0] = new RationalNumber(180, 1);
    38         assertEquals(-180, StreetsideUtils.degMinSecToDouble(num, ref), 0.01);
    39         num[0] = new RationalNumber(190, 1);
    40         assertEquals(170, StreetsideUtils.degMinSecToDouble(num, ref), 0.01);
    41     }
    4219}
  • applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/utils/TestUtil.java

    r36194 r36228  
    1010import java.lang.reflect.Method;
    1111import java.lang.reflect.Modifier;
     12import java.time.Instant;
     13import java.util.Arrays;
    1214
     15import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
    1316import org.openstreetmap.josm.tools.JosmRuntimeException;
    1417
     
    7679        }
    7780    }
     81
     82    /**
     83     * Generate a valid image
     84     * @param id The id of the image
     85     * @param lat The latitude of the image
     86     * @param lon The longitude of the image
     87     * @return The new image
     88     */
     89    public static StreetsideImage generateImage(String id, double lat, double lon) {
     90        return new StreetsideImage("https://ecn.{subdomain}.tiles.virtualearth.net/tiles/hs" + id
     91                + "{faceId}{tileId}?g=14336&key=Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU", lat,
     92                lon, 268.811, 1.395, -4.875, Instant.ofEpochMilli(1614556800000L), Instant.ofEpochMilli(1614643199999L),
     93                "https://dev.virtualearth.net/Branding/logo_powered_by.png",
     94                "Copyright © 2024 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.",
     95                1, 3, 256, 256, Arrays.asList("t0", "t1", "t2", "t3"));
     96    }
    7897}
  • applications/editors/josm/plugins/build.xml

    r36190 r36228  
    1414    <property name="java21_plugins" value="FIT/build.xml" />
    1515    <property name="java17_plugins" value="maproulette/build.xml
     16                                            MicrosoftStreetside/build.xml
    1617                                            imageio/build.xml
    1718                                            pmtiles/build.xml
     
    3233                                            apache-http/build.xml
    3334                                            austriaaddresshelper/build.xml"/>
    34     <property name="javafx_plugins" value="javafx/build.xml
    35                                            MicrosoftStreetside/build.xml"/>
     35    <property name="javafx_plugins" value="javafx/build.xml"/>
    3636
    3737    <!-- We are dropping Java 8 support in January 2024 - these have issues compiling with Java 8, but not with Java 11+ targeting Java 8 -->
Note: See TracChangeset for help on using the changeset viewer.