Changeset 36228 in osm for applications/editors
- Timestamp:
- 2024-03-14T16:34:12+01:00 (10 months ago)
- Location:
- applications/editors/josm/plugins
- Files:
-
- 4 added
- 19 deleted
- 78 edited
Legend:
- Unmodified
- Added
- Removed
-
applications/editors/josm/plugins/MicrosoftStreetside/.classpath
r35601 r36228 28 28 </attributes> 29 29 </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/"/> 31 31 <classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/> 32 32 <classpathentry combineaccessrules="false" kind="src" path="/JOSM"/> … … 35 35 <classpathentry combineaccessrules="false" kind="src" path="/JOSM-apache-commons"/> 36 36 <classpathentry combineaccessrules="false" kind="src" path="/JOSM-apache-http"/> 37 <classpathentry combineaccessrules="false" kind="src" path="/JOSM-utilsplugin2"/>38 37 <classpathentry kind="output" path="bin/default"/> 39 38 </classpath> -
applications/editors/josm/plugins/MicrosoftStreetside/.gitignore
r34432 r36228 9 9 javadoc/ 10 10 build/ 11 bin/12 11 doc/ 13 12 logs/ … … 21 20 # OS X metadata 22 21 .DS_Store 23 24 25 /bin/ -
applications/editors/josm/plugins/MicrosoftStreetside/CONTRIBUTING.md
r34352 r36228 14 14 15 15 The following format of source code files is preferred in this repository: 16 * Indentation with 2spaces per indentation level16 * Indentation with 4 spaces per indentation level 17 17 * line endings should be UNIX-style line endings (LF) 18 18 * one newline (LF) at the end of the file -
applications/editors/josm/plugins/MicrosoftStreetside/README.md
r34428 r36228 26 26 gradle build 27 27 28 Now Restart JOSM and activate the MicrosoftStree side plugin in your preferences.28 Now Restart JOSM and activate the MicrosoftStreetside plugin in your preferences. 29 29 The MicrosoftStreetside menu items will appear in the JOSM main menu after JOSM is 30 30 restarted. … … 34 34 ## License 35 35 36 This plugin is based on the Mapil ary 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.36 This 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. 37 37 38 38 This software is licensed under [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). … … 43 43 Java 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). 44 44 45 Th e 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).45 Third-party JDKs such as Azul have versions with JavaFX included. Please use those. -
applications/editors/josm/plugins/MicrosoftStreetside/build.gradle
r36194 r36228 1 import com.github.spotbugs.snom.Confidence 2 import com.github.spotbugs.snom.Effort 1 3 import com.github.spotbugs.snom.SpotBugsTask 2 import net.ltgt.gradle.errorprone.CheckSeverity4 import org.gradle.api.tasks.testing.logging.TestLogEvent 3 5 4 6 plugins { … … 7 9 id 'jacoco' 8 10 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) 16 19 } 17 20 … … 21 24 //apply from: 'gradle/markdown.gradle' 22 25 23 sourceCompatibility = ' 1.8'26 sourceCompatibility = '21' 24 27 25 28 def versionProcess = new ProcessBuilder("git", "describe", "--always", "--dirty").start() … … 40 43 } 41 44 42 def versions = [ 43 awaitility: "4.2.0", 44 jmockit: "1.49.a", 45 junit: "5.10.1", 46 wiremock: "2.27.2" 47 ] 45 javafx { 46 modules = [ 'javafx.graphics', 'javafx.controls', 'javafx.swing' ] 47 } 48 48 49 49 dependencies { 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 61 58 } 62 59 … … 82 79 } 83 80 } 81 } 82 83 // Spotbugs config 84 spotbugs { 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] 84 90 } 85 91 … … 133 139 } 134 140 135 import org.gradle.api.tasks.testing.logging.TestLogEvent136 137 141 test { 138 142 project.afterEvaluate { -
applications/editors/josm/plugins/MicrosoftStreetside/build.xml
r36194 r36228 10 10 <property name="josm" location="../../core/dist/josm-custom.jar"/> 11 11 <property name="plugin.dist.dir" value="../../dist"/> 12 <property name="java.lang.version" value="21"/> 12 13 <!--** include targets that all plugins have in common **--> 13 14 <import file="../build-common.xml"/> 14 15 <fileset id="plugin.requires.jars" dir="${plugin.dist.dir}"> 15 16 <include name="apache-commons.jar"/> 16 <include name="apache-http.jar"/>17 17 <include name="javafx-osx.jar" if:set="isMac"/> 18 18 <include name="javafx-unixoid.jar" if:set="isUnix"/> 19 19 <include name="javafx-windows.jar" if:set="isWindows"/> 20 <include name="utilsplugin2.jar"/>21 20 </fileset> 22 21 <target name="pre-compile" depends="fetch_dependencies"> -
applications/editors/josm/plugins/MicrosoftStreetside/gradle.properties
r36194 r36228 5 5 plugin.icon=images/streetside-logo.svg 6 6 plugin.link=https://github.com/Microsoft/MicrosoftStreetsidePlugin 7 plugin.minimum.java.version=21 7 8 8 9 # Minimum required JOSM version to run this plugin, choose the lowest version possible that is compatible. 9 10 # You can check if the plugin compiles against this version by executing `./gradlew compileJava_minJosm`. 10 plugin.main.version=18 72311 plugin.main.version=18877 11 12 #plugin.version= 12 13 # Version of JOSM against which the plugin is compiled 13 14 # Please check, if the specified version is available for download from https://josm.openstreetmap.de/download/ . 14 15 # If not, choose the next higher number that is available, or the gradle build will break. 15 plugin.compile.version=18 72416 plugin.requires=apache-commons; apache-http;javafx;utilsplugin216 plugin.compile.version=18911 17 plugin.requires=apache-commons;javafx 17 18 18 19 # 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 config23 spotbugs {24 toolVersion = spotbugsVersion25 ignoreFailures = true26 effort = "max"27 reportLevel = "low"28 //sourceSets = [sourceSets.main, sourceSets.test]29 }30 31 1 // JaCoCo config 32 2 jacoco { 33 toolVersion = jacocoVersion3 toolVersion = libs.versions.jacoco.get() 34 4 } 35 5 jacocoTestReport { 36 6 reports { 37 xml.enabled = true 38 html.destination file("$buildDir/reports/jacoco") 7 xml.required = true 39 8 } 9 dependsOn test 40 10 } 41 11 build.dependsOn jacocoTestReport … … 43 13 // PMD config 44 14 pmd { 45 toolVersion pmdVersion15 toolVersion libs.versions.pmd.get() 46 16 ignoreFailures true 47 17 ruleSetConfig = resources.text.fromFile('config/pmd/ruleset.xml') … … 50 20 51 21 // SonarQube config 52 sonar qube{22 sonar { 53 23 properties { 54 24 property 'sonar.forceAuthentication', 'true' -
applications/editors/josm/plugins/MicrosoftStreetside/gradle/wrapper/gradle-wrapper.properties
r36194 r36228 1 1 distributionBase=GRADLE_USER_HOME 2 2 distributionPath=wrapper/dists 3 distributionSha256Sum=740c2e472ee4326c33bf75a5c9f5cd1e69ecf3f9b580f6e236c86d1f3d98cfac 4 distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip 3 distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c 4 distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 5 networkTimeout=10000 6 validateDistributionUrl=true 5 7 zipStoreBase=GRADLE_USER_HOME 6 8 zipStorePath=wrapper/dists -
applications/editors/josm/plugins/MicrosoftStreetside/gradlew
r36194 r36228 1 #!/ usr/bin/envsh2 3 # 4 # Copyright 2015 the original author orauthors.1 #!/bin/sh 2 3 # 4 # Copyright © 2015-2021 the original authors. 5 5 # 6 6 # Licensed under the Apache License, Version 2.0 (the "License"); … … 18 18 19 19 ############################################################################## 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 # 23 63 ############################################################################## 24 64 25 65 # Attempt to set APP_HOME 66 26 67 # 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 68 app_path=$0 69 70 # Need this for daisy-chained symlinks. 71 while 72 APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 [ -h "$app_path" ] 74 do 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 37 81 done 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 85 APP_BASE_NAME=${0##*/} 86 # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 48 88 49 89 # Use the maximum available, or set MAX_FD != -1 to use that value. 50 MAX_FD= "maximum"90 MAX_FD=maximum 51 91 52 92 warn () { 53 93 echo "$*" 54 } 94 } >&2 55 95 56 96 die () { … … 59 99 echo 60 100 exit 1 61 } 101 } >&2 62 102 63 103 # OS specific support (must be 'true' or 'false'). … … 66 106 darwin=false 67 107 nonstop=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 ;; 108 case "$( uname )" in #( 109 CYGWIN* ) cygwin=true ;; #( 110 Darwin* ) darwin=true ;; #( 111 MSYS* | MINGW* ) msys=true ;; #( 112 NONSTOP* ) nonstop=true ;; 81 113 esac 82 114 … … 88 120 if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 121 # 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 91 123 else 92 JAVACMD= "$JAVA_HOME/bin/java"124 JAVACMD=$JAVA_HOME/bin/java 93 125 fi 94 126 if [ ! -x "$JAVACMD" ] ; then … … 99 131 fi 100 132 else 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. 103 137 104 138 Please set the JAVA_HOME variable in your environment to match the 105 139 location of your Java installation." 140 fi 106 141 fi 107 142 108 143 # 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" 144 if ! "$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 160 fi 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 171 if "$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" ) 114 188 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 142 198 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" 199 fi 200 201 202 # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 DEFAULT_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 211 set -- \ 212 "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 -classpath "$CLASSPATH" \ 214 org.gradle.wrapper.GradleWrapperMain \ 215 "$@" 216 217 # Stop when "xargs" is not available. 218 if ! command -v xargs >/dev/null 2>&1 219 then 220 die "xargs is not available" 221 fi 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 242 eval "set -- $( 243 printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 xargs -n1 | 245 sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 tr '\n' ' ' 247 )" '"$@"' 184 248 185 249 exec "$JAVACMD" "$@" -
applications/editors/josm/plugins/MicrosoftStreetside/gradlew.bat
r35779 r36228 15 15 @rem 16 16 17 @if "%DEBUG%" =="" @echo off17 @if "%DEBUG%"=="" @echo off 18 18 @rem ########################################################################## 19 19 @rem … … 26 26 27 27 set DIRNAME=%~dp0 28 if "%DIRNAME%" == "" set DIRNAME=. 28 if "%DIRNAME%"=="" set DIRNAME=. 29 @rem This is normally unused 29 30 set APP_BASE_NAME=%~n0 30 31 set APP_HOME=%DIRNAME% … … 41 42 set JAVA_EXE=java.exe 42 43 %JAVA_EXE% -version >NUL 2>&1 43 if "%ERRORLEVEL%" == "0"goto execute44 if %ERRORLEVEL% equ 0 goto execute 44 45 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. 46 echo. 1>&2 47 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 echo. 1>&2 49 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 echo location of your Java installation. 1>&2 50 51 51 52 goto fail … … 57 58 if exist "%JAVA_EXE%" goto execute 58 59 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. 60 echo. 1>&2 61 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 echo. 1>&2 63 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 echo location of your Java installation. 1>&2 64 65 65 66 goto fail … … 76 77 :end 77 78 @rem End local scope for the variables with windows NT shell 78 if "%ERRORLEVEL%"=="0"goto mainEnd79 if %ERRORLEVEL% equ 0 goto mainEnd 79 80 80 81 :fail 81 82 rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 83 rem the _cmd.exe /c_ return code! 83 if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 exit /b 1 84 set EXIT_CODE=%ERRORLEVEL% 85 if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 exit /b %EXIT_CODE% 85 88 86 89 :mainEnd -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideAbstractImage.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside; 3 3 4 import org.openstreetmap.josm.data.coor.LatLon; 4 import java.io.Serializable; 5 import java.util.List; 6 import java.util.stream.IntStream; 7 import java.util.stream.Stream; 8 9 import org.openstreetmap.gui.jmapviewer.TileXY; 10 import org.openstreetmap.josm.data.IQuadBucketType; 11 import org.openstreetmap.josm.data.coor.ILatLon; 12 import org.openstreetmap.josm.data.osm.BBox; 13 import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils; 14 import org.openstreetmap.josm.tools.Pair; 15 16 import jakarta.annotation.Nonnull; 5 17 6 18 /** 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}. 9 21 * 10 22 * @author nokutu … … 12 24 * 13 25 */ 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; 26 public sealed 20 27 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; 28 interface StreetsideAbstractImage extends ILatLon, IQuadBucketType, Comparable<StreetsideAbstractImage>, Serializable 29 permits StreetsideImage 30 { 60 31 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(); 80 37 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(); 88 43 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(); 91 50 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(); 98 56 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(); 105 62 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 } 114 71 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 } 118 80 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 } 127 88 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); 131 92 } 132 }133 93 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 } 142 102 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 } 152 133 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 } 158 146 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); 166 177 } 167 }168 178 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); 185 191 } 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 visible228 * 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 position236 *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 next246 * 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 previous258 * 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 has270 * 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 he281 * The angle the image is moving.282 */283 public void turn(final double he) {284 movingHe = tempHe + he;285 }286 287 /**288 * @return the ne289 */290 public long getNe() {291 return ne;292 }293 294 /**295 * @param ne the ne to set296 */297 public void setNe(long ne) {298 this.ne = ne;299 }300 301 /**302 * @return the pr303 */304 public long getPr() {305 return pr;306 }307 308 /**309 * @param pr the pr to set310 */311 public void setPr(long pr) {312 this.pr = pr;313 }314 315 192 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideData.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside; 3 3 4 import java.util.ArrayList; 4 5 import java.util.Arrays; 5 6 import java.util.Collection; 6 7 import java.util.Collections; 8 import java.util.Comparator; 9 import java.util.HashSet; 7 10 import java.util.List; 8 11 import java.util.Objects; 9 import java.util.Set;10 import java.util.concurrent.ConcurrentHashMap;11 12 import java.util.concurrent.CopyOnWriteArrayList; 12 import java.util. stream.Collectors;13 import java.util.function.Consumer; 13 14 14 15 import org.apache.commons.jcs3.access.CacheAccess; … … 17 18 import org.openstreetmap.josm.data.DataSource; 18 19 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 20 import org.openstreetmap.josm.data.coor.LatLon; 21 import org.openstreetmap.josm.data.osm.BBox; 22 import org.openstreetmap.josm.data.osm.QuadBuckets; 19 23 import org.openstreetmap.josm.gui.MainApplication; 20 import org.openstreetmap.josm.gui.MapView;21 24 import org.openstreetmap.josm.plugins.streetside.cache.CacheUtils; 22 25 import org.openstreetmap.josm.plugins.streetside.cache.Caches; … … 32 35 * @author renerr18 (extended for Streetside) 33 36 * @see StreetsideAbstractImage 34 * @see StreetsideSequence35 37 */ 36 38 public 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 } 423 399 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideDataListener.java
r36194 r36228 10 10 public interface StreetsideDataListener { 11 11 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(); 16 16 17 /**18 * Fired when the selected image is changed by something different from19 * 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); 25 25 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideImage.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside; 3 3 4 import java.time.Instant; 4 5 import java.util.List; 5 6 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; 7 import jakarta.annotation.Nonnull; 9 8 10 9 /** 11 10 * A StreetsideImage object represents each of the images stored in Streetside. 12 11 * 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 * 13 28 * @author nokutu 14 29 * @author renerr18 15 30 * 16 * @see StreetsideSequence17 31 * @see StreetsideData 18 32 */ 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; 33 public 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 } 38 43 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(); 100 61 } 101 return hashCode() - image.hashCode();102 }103 104 @Override105 public int hashCode() {106 return id.hashCode();107 }108 109 @Override110 public void stopMoving() {111 super.stopMoving();112 checkModified();113 }114 115 private void checkModified() {116 // modifications not currently supported in Streetside117 }118 119 @Override120 public void turn(double ca) {121 super.turn(ca);122 checkModified();123 }124 125 /**126 * @return the altitude127 */128 public double getAl() {129 return al;130 }131 132 /**133 * @param altitude the altitude to set134 */135 public void setAl(double altitude) {136 al = altitude;137 }138 139 /**140 * @return the roll141 */142 public double getRo() {143 return ro;144 }145 146 /**147 * @param roll the roll to set148 */149 public void setRo(double roll) {150 ro = roll;151 }152 153 /**154 * @return the pi155 */156 public double getPi() {157 return pi;158 }159 160 /**161 * @param pitch the pi to set162 */163 public void setPi(double pitch) {164 pi = pitch;165 }166 167 /**168 * @return the burringl169 */170 public String getBl() {171 return bl;172 }173 174 /**175 * @param blurring the blurring to set176 */177 public void setBl(String blurring) {178 bl = blurring;179 }180 181 /**182 * @return the ml183 */184 public int getMl() {185 return ml;186 }187 188 /**189 * @param ml the ml to set190 */191 public void setMl(int ml) {192 this.ml = ml;193 }194 195 /**196 * @return the nbn197 */198 public List<String> getNbn() {199 return nbn;200 }201 202 /**203 * @param nbn the nbn to set204 */205 public void setNbn(List<String> nbn) {206 this.nbn = nbn;207 }208 209 /**210 * @return the pbn211 */212 public List<String> getPbn() {213 return pbn;214 }215 216 /**217 * @param pbn the pbn to set218 */219 public void setPbn(List<String> pbn) {220 this.pbn = pbn;221 }222 223 /**224 * @return the ad225 */226 public int getAd() {227 return ad;228 }229 230 /**231 * @param ad the ad to set232 */233 public void setAd(int ad) {234 this.ad = ad;235 }236 237 /**238 * @return the la239 */240 public double getLa() {241 return la;242 }243 244 /**245 * @param la the la to set246 */247 public void setLa(double la) {248 this.la = la;249 }250 251 /**252 * @return the lo253 */254 public double getLo() {255 return lo;256 }257 258 /**259 * @param lo the lo to set260 */261 public void setLo(double lo) {262 this.lo = lo;263 }264 265 /**266 * @return the rn267 */268 public Rn getRn() {269 return rn;270 }271 272 /**273 * @param rn the rn to set274 */275 public void setRn(Rn rn) {276 this.rn = rn;277 }278 279 /**280 * Rn is a Bing Streetside image attribute - currently not281 * 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 }287 62 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideLayer.java
r36194 r36228 5 5 import java.awt.BasicStroke; 6 6 import java.awt.Color; 7 import java.awt.Composite;8 7 import java.awt.Graphics2D; 9 8 import java.awt.GraphicsEnvironment; 10 import java.awt.Point;11 9 import java.awt.Rectangle; 12 10 import java.awt.RenderingHints; … … 15 13 import java.awt.image.BufferedImage; 16 14 import java.util.Comparator; 17 import java.util.IntSummaryStatistics; 18 import java.util.Optional; 15 import java.util.Objects; 19 16 import java.util.logging.Logger; 20 17 … … 23 20 24 21 import org.openstreetmap.josm.data.Bounds; 25 import org.openstreetmap.josm.data.coor.ILatLon;26 import org.openstreetmap.josm.data.osm.DataSet;27 22 import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 28 23 import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter; … … 39 34 import org.openstreetmap.josm.plugins.streetside.cache.CacheUtils; 40 35 import org.openstreetmap.josm.plugins.streetside.gui.StreetsideMainDialog; 41 import org.openstreetmap.josm.plugins.streetside.history.StreetsideRecord;42 36 import org.openstreetmap.josm.plugins.streetside.io.download.StreetsideDownloader; 43 37 import org.openstreetmap.josm.plugins.streetside.io.download.StreetsideDownloader.DOWNLOAD_MODE; … … 59 53 */ 60 54 public 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; 284 308 } 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 490 386 } 491 387 492 388 /* (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 } 502 448 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsidePlugin.java
r36194 r36228 11 11 import org.openstreetmap.josm.plugins.streetside.actions.StreetsideDownloadAction; 12 12 import org.openstreetmap.josm.plugins.streetside.actions.StreetsideDownloadViewAction; 13 import org.openstreetmap.josm.plugins.streetside.actions.StreetsideExportAction;14 13 import org.openstreetmap.josm.plugins.streetside.actions.StreetsideWalkAction; 15 14 import org.openstreetmap.josm.plugins.streetside.actions.StreetsideZoomAction; … … 20 19 import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.ImageInfoHelpPopup; 21 20 import 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;24 21 import org.openstreetmap.josm.tools.ImageProvider; 25 22 … … 29 26 public class StreetsidePlugin extends Plugin { 30 27 31 public static final ImageProvider LOGO = new ImageProvider("streetside-logo");28 public static final ImageProvider LOGO = new ImageProvider("streetside-logo"); 32 29 33 /**34 * Zoom action35 */36 private static final StreetsideZoomAction ZOOM_ACTION = new StreetsideZoomAction();37 /**38 * Walk action39 */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(); 41 38 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); 49 46 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); 52 51 } 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 }59 52 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 } 63 56 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 } 70 64 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; 78 75 } 79 return null;80 }81 76 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 } 92 94 } 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(); 98 99 } 99 }100 101 @Override102 public PreferenceSetting getPreferenceSetting() {103 return new StreetsidePreferenceSetting();104 }105 100 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/ImageReloadAction.java
r36194 r36228 3 3 4 4 import java.awt.event.ActionEvent; 5 import java.io.Serial; 5 6 6 7 import javax.swing.AbstractAction; … … 13 14 public class ImageReloadAction extends AbstractAction { 14 15 15 private static final long serialVersionUID = 7987479726049238315L; 16 @Serial 17 private static final long serialVersionUID = 7987479726049238315L; 16 18 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 } 20 22 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 } 25 28 } 26 }27 29 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideDownloadAction.java
r36194 r36228 6 6 import java.awt.event.ActionEvent; 7 7 import java.awt.event.KeyEvent; 8 import java.io.Serial; 8 9 import java.util.logging.Level; 9 10 import java.util.logging.Logger; … … 27 28 public class StreetsideDownloadAction extends JosmAction { 28 29 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()); 33 35 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); 48 42 } 49 43 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 } 61 64 } 62 }63 65 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideDownloadViewAction.java
r36194 r36228 4 4 import java.awt.event.ActionEvent; 5 5 import java.awt.event.KeyEvent; 6 import java.io.Serial; 6 7 7 8 import org.openstreetmap.josm.actions.JosmAction; … … 25 26 public class StreetsideDownloadViewAction extends JosmAction implements ValueChangeListener<String> { 26 27 27 private static final long serialVersionUID = 6738276777802831669L; 28 @Serial 29 private static final long serialVersionUID = 6738276777802831669L; 28 30 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"); 30 32 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 } 42 44 43 @Override44 public void actionPerformed(ActionEvent arg0) {45 StreetsideDownloader.downloadVisibleArea();46 }45 @Override 46 public void actionPerformed(ActionEvent arg0) { 47 StreetsideDownloader.downloadVisibleArea(); 48 } 47 49 48 @Override49 protected boolean listenToSelectionChange() {50 return false;51 }50 @Override 51 protected boolean listenToSelectionChange() { 52 return false; 53 } 52 54 53 /**54 * Enabled when the Streetside layer is instantiated and download mode is either "osm area" or "manual".55 */56 @Override57 protected void updateEnabledState() {58 super.updateEnabledState();59 setEnabled(StreetsideLayer.hasInstance()60 && (StreetsideDownloader.getMode() == StreetsideDownloader.DOWNLOAD_MODE.OSM_AREA61 || 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 } 63 65 64 @Override65 public void valueChanged(ValueChangeEvent<? extends String> e) {66 updateEnabledState();67 }66 @Override 67 public void valueChanged(ValueChangeEvent<? extends String> e) { 68 updateEnabledState(); 69 } 68 70 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideWalkAction.java
r36194 r36228 6 6 import java.awt.Dimension; 7 7 import java.awt.event.ActionEvent; 8 import java.io.Serial; 8 9 import java.util.ArrayList; 9 10 import java.util.List; … … 14 15 import org.openstreetmap.josm.actions.JosmAction; 15 16 import org.openstreetmap.josm.gui.MainApplication; 16 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;17 17 import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener; 18 import org.openstreetmap.josm.plugins.streetside.StreetsideImage; 18 19 import org.openstreetmap.josm.plugins.streetside.StreetsideLayer; 19 20 import org.openstreetmap.josm.plugins.streetside.StreetsidePlugin; … … 31 32 public class StreetsideWalkAction extends JosmAction implements StreetsideDataListener { 32 33 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; 36 38 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 } 44 46 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 } 58 61 } 59 }60 62 61 @Override62 public void imagesAdded() {63 // Nothing64 }63 @Override 64 public void imagesAdded() { 65 // Nothing 66 } 65 67 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 } 74 76 75 /**76 * Removes a listener.77 *78 * @param lis79 * 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 } 84 86 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 } 88 94 } 89 for (WalkListener lis : listeners) { 90 lis.walkStarted(thread); 95 96 @Override 97 protected boolean listenToSelectionChange() { 98 return false; 91 99 } 92 }93 100 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 } 98 109 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); 105 117 } 106 }107 108 /**109 * Enabled when a mapillary image is selected.110 */111 @Override112 protected void updateEnabledState() {113 super.updateEnabledState();114 setEnabled(StreetsideLayer.hasInstance() && StreetsideLayer.getInstance().getData().getSelectedImage() != null);115 }116 118 117 119 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideZoomAction.java
r36194 r36228 5 5 6 6 import java.awt.event.ActionEvent; 7 import java.io.Serial; 7 8 8 9 import org.openstreetmap.josm.actions.JosmAction; 9 10 import org.openstreetmap.josm.gui.MainApplication; 10 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;11 11 import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener; 12 import org.openstreetmap.josm.plugins.streetside.StreetsideImage; 12 13 import org.openstreetmap.josm.plugins.streetside.StreetsideLayer; 13 14 import org.openstreetmap.josm.plugins.streetside.StreetsidePlugin; … … 23 24 public class StreetsideZoomAction extends JosmAction implements StreetsideDataListener { 24 25 25 private static final long serialVersionUID = -5885977359895624233L; 26 @Serial 27 private static final long serialVersionUID = -5885977359895624233L; 26 28 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 } 34 36 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()); 39 43 } 40 MainApplication.getMap().mapView41 .zoomTo(StreetsideLayer.getInstance().getData().getSelectedImage().getMovingLatLon());42 }43 44 44 @Override45 public void imagesAdded() {46 // Nothing47 }45 @Override 46 public void imagesAdded() { 47 // Nothing 48 } 48 49 49 @Override50 protected boolean listenToSelectionChange() {51 return false;52 }50 @Override 51 protected boolean listenToSelectionChange() { 52 return false; 53 } 53 54 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 } 60 62 } 61 }62 63 63 @Override64 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 } 68 69 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/WalkListener.java
r34317 r36228 11 11 public interface WalkListener { 12 12 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); 19 19 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/WalkThread.java
r36194 r36228 8 8 import javax.swing.SwingUtilities; 9 9 10 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;11 10 import org.openstreetmap.josm.plugins.streetside.StreetsideData; 12 11 import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener; … … 24 23 */ 25 24 public 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; 35 34 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 the41 * 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 } 53 52 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 } 67 67 } 68 }69 68 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; 86 122 } 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 } 94 139 } 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;115 140 } 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;123 141 } 124 end();125 }126 142 127 private void preDownloadCubemaps(StreetsideImage startImage, int n) { 128 if (n >= 1 && startImage != null) { 143 @Override 144 public void imagesAdded() { 145 // Nothing 146 } 129 147 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 } 133 155 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); 139 179 } 140 }141 180 } 142 }143 181 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 } 154 193 } 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 }195 194 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cache/CacheUtils.java
r36194 r36228 3 3 4 4 import java.io.IOException; 5 import java.util.logging.Logger; 5 import java.io.UncheckedIOException; 6 import java.util.Collections; 7 import java.util.HashMap; 8 import java.util.Map; 9 import java.util.stream.Collectors; 6 10 7 11 import org.openstreetmap.josm.data.cache.CacheEntry; 8 12 import org.openstreetmap.josm.data.cache.CacheEntryAttributes; 9 13 import org.openstreetmap.josm.data.cache.ICachedLoaderListener; 14 import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY; 10 15 import org.openstreetmap.josm.plugins.streetside.StreetsideImage; 11 16 import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapBuilder; 12 import org.openstreetmap.josm. tools.Logging;17 import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils; 13 18 14 19 /** … … 20 25 public final class CacheUtils { 21 26 22 private static final Logger LOGGER = Logger.getLogger(CacheUtils.class.getCanonicalName());27 private static final IgnoreDownload ignoreDownload = new IgnoreDownload(); 23 28 24 private static final IgnoreDownload ignoreDownload = new IgnoreDownload(); 29 private CacheUtils() { 30 // Private constructor to avoid instantiation 31 } 25 32 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 } 29 43 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 } 39 54 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 } 49 67 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 }; 82 96 } 83 }84 97 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 } 98 114 } 99 }100 115 101 /**102 * Picture quality103 */104 public enum PICTURE {105 116 /** 106 * Thumbnail quality picture (320 p)117 * Picture quality 107 118 */ 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 } 122 137 123 private static class IgnoreDownload implements ICachedLoaderListener {138 private static class IgnoreDownload implements ICachedLoaderListener { 124 139 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 } 128 144 } 129 }130 145 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cache/Caches.java
r36194 r36228 10 10 11 11 import org.apache.commons.jcs3.access.CacheAccess; 12 import org.apache.commons.jcs3.engine.behavior.IElementAttributes;13 12 import org.openstreetmap.josm.data.Preferences; 14 13 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 15 14 import org.openstreetmap.josm.data.cache.JCSCacheManager; 16 import org.openstreetmap.josm.plugins.streetside.model.UserProfile;17 15 import org.openstreetmap.josm.tools.Logging; 18 16 19 17 public final class Caches { 20 18 21 private static final Logger LOGGER = Logger.getLogger(Caches.class.getCanonicalName());19 private static final Logger LOGGER = Logger.getLogger(Caches.class.getCanonicalName()); 22 20 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 47 23 } 48 24 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; 53 33 } 54 34 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; 61 37 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 } 65 48 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 } 75 60 } 76 61 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; 81 75 } 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 } 84 89 } 85 90 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; 90 94 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 } 94 105 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 } 104 118 } 105 119 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 } 110 130 } 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 } 113 136 } 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 @Override133 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 @Override151 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 }160 137 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cache/StreetsideCache.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside.cache; 3 3 4 import java.io.UncheckedIOException; 5 import java.net.MalformedURLException; 6 import java.net.URI; 4 7 import java.net.URL; 5 8 import java.util.HashMap; … … 8 11 import org.openstreetmap.josm.data.cache.JCSCachedTileLoaderJob; 9 12 import org.openstreetmap.josm.data.imagery.TileJobOptions; 10 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL.VirtualEarth;11 13 12 14 /** … … 18 20 public class StreetsideCache extends JCSCachedTileLoaderJob<String, BufferedImageCacheEntry> { 19 21 20 private final URL url; 21 private final String id; 22 private final String url; 22 23 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 or28 * 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 } 32 33 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; 39 37 } 40 }41 38 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 } 46 47 47 @Override48 public URL getUrl() {49 return url;50 }48 @Override 49 protected BufferedImageCacheEntry createCacheEntry(byte[] content) { 50 return new BufferedImageCacheEntry(content); 51 } 51 52 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; 61 60 } 62 final byte[] content = cacheData.getContent();63 return content != null && content.length > 0;64 }65 66 /**67 * Types of images.68 *69 * @author nokutu70 */71 public enum Type {72 /**73 * Full quality image74 */75 FULL_IMAGE,76 /**77 * Low quality image78 */79 THUMBNAIL80 }81 61 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cubemap/CameraTransformer.java
r36194 r36228 7 7 import javafx.scene.transform.Translate; 8 8 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 */ 11 12 public class CameraTransformer extends Group { 12 13 13 publicTranslate t = new Translate();14 publicTranslate p = new Translate();15 publicTranslate ip = new Translate();16 publicRotate rx = new Rotate();17 publicRotate ry = new Rotate();18 publicRotate rz = new Rotate();19 publicScale 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(); 20 21 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 } 24 32 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 } 28 60 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 } 32 66 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 } 37 71 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); 59 74 } 60 }61 75 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 } 67 79 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 } 72 83 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 } 76 89 77 public void setTy(double y) {78 t.setY(y);79 }90 public void setRotateX(double x) { 91 rx.setAngle(x); 92 } 80 93 81 public void setTz(double z) {82 t.setZ(z);83 }94 public void setRotateY(double y) { 95 ry.setAngle(y); 96 } 84 97 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 } 90 101 91 public void setRotateX(double x) {92 rx.setAngle(x);93 }102 public void setRx(double x) { 103 rx.setAngle(x); 104 } 94 105 95 public void setRotateY(double y) {96 ry.setAngle(y);97 }106 public void setRy(double y) { 107 ry.setAngle(y); 108 } 98 109 99 public void setRotateZ(double z) {100 rz.setAngle(z);101 }110 public void setRz(double z) { 111 rz.setAngle(z); 112 } 102 113 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 } 106 119 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 } 110 125 111 public void setRz(double z) {112 rz.setAngle(z);113 }126 public void setSx(double x) { 127 s.setX(x); 128 } 114 129 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 } 120 133 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 } 126 137 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 } 130 146 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 } 134 153 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 } 138 168 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 } 184 172 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cubemap/CubemapBox.java
r36194 r36228 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.plugins.streetside.cubemap; 3 4 import java.awt.image.BufferedImage;5 3 6 4 import org.openstreetmap.josm.plugins.streetside.utils.GraphicsUtils; … … 9 7 import javafx.beans.property.DoubleProperty; 10 8 import javafx.beans.property.SimpleDoubleProperty; 11 import javafx.geometry.Rectangle2D;12 9 import javafx.scene.Group; 13 10 import javafx.scene.PerspectiveCamera; … … 19 16 20 17 /** 18 * A box for showing the cubemap images 21 19 * @author renerr18 22 20 */ 23 @SuppressWarnings("restriction")24 21 public class CubemapBox extends Group { 25 22 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; 44 38 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(); 52 53 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()); 54 60 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; 57 69 58 super();70 loadImageViews(); 59 71 60 imageType = CubemapBoxImageType.MULTIPLE;72 getTransforms().add(affine); 61 73 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); 70 75 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(); 85 77 } 86 78 87 validateImageType(); 88 } 79 /** 80 * Load the image views 81 */ 82 public void loadImageViews() { 89 83 90 private void layoutViews() { 84 for (ImageView iv : views) { 85 iv.setSmooth(true); 86 iv.setPreserveRatio(true); 87 } 91 88 92 for (ImageView v : views) { 93 v.setFitWidth(getSize()); 94 v.setFitHeight(getSize()); 89 validateImageType(); 95 90 } 96 91 97 back.setTranslateX(-0.5 * getSize()); 98 back.setTranslateY(-0.5 * getSize()); 99 back.setTranslateZ(-0.5 * getSize()); 92 private void layoutViews() { 100 93 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 } 108 98 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()); 113 102 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()); 118 110 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); 123 115 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); 128 120 129 } 121 left.setTranslateX(-1 * getSize()); 122 left.setTranslateY(-0.5 * getSize()); 123 left.setRotationAxis(Rotate.Y_AXIS); 124 left.setRotate(90); 130 125 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); 142 130 } 143 }144 131 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 } 149 138 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 }); 153 150 } 154 double cellSize = singleImg.getWidth() - singleImg.getHeight();155 151 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 } 157 169 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 } 170 173 171 // add top padding x+, y+, width-, height 172 up.setViewport(new Rectangle2D(topx, topy, cellSize, cellSize)); 174 private final DoubleProperty size = new SimpleDoubleProperty(); 173 175 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; 191 178 } 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 @Override220 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 * Properties240 */241 242 private final DoubleProperty size = new SimpleDoubleProperty() {243 @Override244 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, SINGLE266 }267 268 179 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cubemap/CubemapBuilder.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside.cubemap; 3 3 4 import java.awt.geom.AffineTransform; 5 import java.awt.image.AffineTransformOp; 4 6 import java.awt.image.BufferedImage; 5 7 import java.text.MessageFormat; 6 8 import java.util.ArrayList; 9 import java.util.Collections; 7 10 import java.util.EnumSet; 8 import java.util.HashMap;9 11 import java.util.List; 10 12 import java.util.Map; … … 15 17 import java.util.concurrent.Executors; 16 18 import java.util.concurrent.Future; 19 import java.util.concurrent.TimeUnit; 20 import java.util.concurrent.atomic.AtomicInteger; 17 21 import java.util.logging.Logger; 22 import java.util.stream.Collectors; 18 23 19 24 import org.openstreetmap.josm.gui.MainApplication; 25 import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY; 20 26 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage; 21 import org.openstreetmap.josm.plugins.streetside.StreetsideCubemap;22 27 import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener; 28 import org.openstreetmap.josm.plugins.streetside.StreetsideImage; 29 import org.openstreetmap.josm.plugins.streetside.cache.StreetsideCache; 23 30 import org.openstreetmap.josm.plugins.streetside.gui.StreetsideViewerDialog; 24 31 import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.StreetsideViewerPanel; 25 import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.ThreeSixtyDegreeViewerPanel;26 32 import org.openstreetmap.josm.plugins.streetside.utils.GraphicsUtils; 27 33 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties; 28 34 import org.openstreetmap.josm.tools.Logging; 29 35 36 import javafx.embed.swing.SwingFXUtils; 30 37 import javafx.scene.image.Image; 31 import javafx.scene.image.ImageView; 32 38 39 /** 40 * Build a cubemap 41 */ 33 42 // 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)); 43 public 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 } 166 219 }); 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; 206 275 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 258 343 long endTime = System.currentTimeMillis(); 259 344 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 } 387 371 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cubemap/CubemapUtils.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside.cubemap; 3 3 4 import java.text.MessageFormat;5 import java.util.HashMap;6 import java.util.Map;7 4 import java.util.logging.Logger; 8 import java.util.stream.Stream;9 5 6 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage; 10 7 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties; 11 8 import org.openstreetmap.josm.tools.Logging; 12 9 13 public class CubemapUtils { 10 /** 11 * Utils for cubemaps 12 */ 13 public final class CubemapUtils { 14 14 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; 22 19 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 64 22 } 65 23 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()); 69 34 } 70 35 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); 75 43 } 76 44 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); 86 52 } 87 53 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 } 101 67 } 102 68 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"); 104 74 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 } 107 104 } 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-pixel143 // overlap144 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-pixel153 // overlap154 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 // back167 case "03":168 tileAddress = 0;169 break;170 // down171 case "12":172 tileAddress = 1;173 break;174 // front175 case "01":176 tileAddress = 2;177 break;178 // left179 case "10":180 tileAddress = 3;181 break;182 // right183 case "02":184 tileAddress = 4;185 break;186 // up187 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 positions200 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 happen256 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 }307 105 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cubemap/ITileDownloadingTaskListener.java
r36194 r36228 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.plugins.streetside.cubemap; 3 4 import java.awt.image.BufferedImage; 5 6 import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY; 7 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage; 3 8 4 9 /** … … 10 15 public interface ITileDownloadingTaskListener { 11 16 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); 18 25 19 26 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/cubemap/TileDownloadingTask.java
r36194 r36228 4 4 import java.awt.image.BufferedImage; 5 5 import java.io.IOException; 6 import java.net.URI; 6 7 import java.text.MessageFormat; 7 8 import java.util.ArrayList; 9 import java.util.Collections; 8 10 import java.util.List; 9 11 import java.util.Objects; 10 12 import java.util.concurrent.Callable; 11 13 import java.util.concurrent.CopyOnWriteArrayList; 14 import java.util.concurrent.Semaphore; 12 15 import java.util.logging.Logger; 13 16 14 17 import javax.imageio.ImageIO; 15 18 16 import org.openstreetmap.josm.plugins.streetside.cache.StreetsideCache; 19 import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY; 20 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage; 17 21 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties; 18 import org.openstreetmap.josm. plugins.streetside.utils.StreetsideURL;22 import org.openstreetmap.josm.spi.preferences.Config; 19 23 import org.openstreetmap.josm.tools.Logging; 20 24 25 /** 26 * A task for downloading tiles of an image 27 */ 21 28 public class TileDownloadingTask implements Callable<List<String>> { 22 29 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; 32 41 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 } 38 55 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 } 47 64 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 } 54 72 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 } 61 88 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<>(); 68 91 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 } 75 104 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; 82 107 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(); 89 109 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()); 96 112 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 } 99 116 100 List<String> res = new ArrayList<>();117 fireTileAdded(this.image, tile, img); 101 118 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; 112 127 } 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; 125 129 } 126 return res;127 }128 130 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)); 154 139 } 155 return tileId;156 }157 158 private void fireTileAdded(String id) {159 listeners.stream().filter(Objects::nonNull).forEach(lis -> lis.tileAdded(id));160 }161 140 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsideImageDisplay.java
r36194 r36228 12 12 import java.awt.Point; 13 13 import java.awt.Rectangle; 14 import java.awt.Shape;15 14 import java.awt.event.MouseEvent; 16 15 import java.awt.event.MouseListener; … … 21 20 import java.awt.geom.Rectangle2D; 22 21 import java.awt.image.BufferedImage; 23 import java.util.ArrayList; 24 import java.util.Collection; 22 import java.io.Serial; 25 23 26 24 import javax.swing.JComponent; … … 28 26 import org.openstreetmap.josm.plugins.streetside.StreetsideLayer; 29 27 import 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;33 28 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties; 34 29 … … 43 38 public class StreetsideImageDisplay extends JComponent { 44 39 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. 275 153 */ 276 154 @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)); 301 170 } 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) { 324 204 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 } 534 485 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsideMainDialog.java
r36194 r36228 9 9 import java.io.ByteArrayInputStream; 10 10 import java.io.IOException; 11 import java.io.Serial; 11 12 import java.text.MessageFormat; 12 13 import java.util.Arrays; 14 import java.util.HashMap; 13 15 import java.util.List; 16 import java.util.Map; 17 import java.util.Optional; 14 18 import java.util.logging.Logger; 19 import java.util.stream.Collectors; 15 20 16 21 import javax.imageio.ImageIO; … … 21 26 import javax.swing.SwingUtilities; 22 27 28 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 23 29 import org.openstreetmap.josm.data.cache.CacheEntry; 24 30 import org.openstreetmap.josm.data.cache.CacheEntryAttributes; … … 26 32 import org.openstreetmap.josm.gui.SideButton; 27 33 import org.openstreetmap.josm.gui.dialogs.ToggleDialog; 34 import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY; 28 35 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage; 29 36 import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener; … … 34 41 import org.openstreetmap.josm.plugins.streetside.actions.WalkThread; 35 42 import org.openstreetmap.josm.plugins.streetside.cache.StreetsideCache; 43 import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils; 36 44 import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.ImageInfoHelpPopup; 45 import org.openstreetmap.josm.plugins.streetside.utils.GraphicsUtils; 37 46 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties; 38 47 import org.openstreetmap.josm.tools.I18n; … … 48 57 public final class StreetsideMainDialog extends ToggleDialog implements ICachedLoaderListener, StreetsideDataListener { 49 58 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 */ 376 293 @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); 398 349 } 399 350 400 351 @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(); 422 363 } 423 364 424 365 @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 } 548 591 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsidePreferenceSetting.java
r36194 r36228 3 3 4 4 import java.awt.BorderLayout; 5 import java.awt.Color;6 5 import java.awt.GridBagConstraints; 7 6 import java.awt.GridBagLayout; 8 import java.awt.event.ActionEvent;9 7 import java.io.IOException; 10 8 import java.io.InputStream; 9 import java.util.Objects; 11 10 import java.util.logging.Logger; 12 11 13 12 import javax.imageio.ImageIO; 14 import javax.swing.AbstractAction;15 13 import javax.swing.BorderFactory; 16 14 import javax.swing.Box; 17 import javax.swing.BoxLayout;18 15 import javax.swing.ImageIcon; 19 import javax.swing.JButton;20 16 import javax.swing.JCheckBox; 21 17 import javax.swing.JComboBox; … … 25 21 import javax.swing.JSpinner; 26 22 import javax.swing.SpinnerNumberModel; 27 import javax.swing.SwingUtilities;28 23 29 24 import org.openstreetmap.josm.actions.ExpertToggleAction; … … 32 27 import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting; 33 28 import org.openstreetmap.josm.plugins.streetside.StreetsidePlugin; 34 import org.openstreetmap.josm.plugins.streetside.gui.boilerplate.StreetsideButton;35 29 import 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;39 30 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideColorScheme; 40 31 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties; … … 49 40 * 50 41 */ 51 public class StreetsidePreferenceSetting implements SubPreferenceSetting , StreetsideLoginListener{42 public class StreetsidePreferenceSetting implements SubPreferenceSetting { 52 43 53 private static final Logger logger = Logger.getLogger(StreetsidePreferenceSetting.class.getCanonicalName());44 private static final Logger logger = Logger.getLogger(StreetsidePreferenceSetting.class.getCanonicalName()); 54 45 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() }); 58 49 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); 80 67 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(); 219 71 } 220 72 221 73 @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 } 226 131 } 227 }228 132 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()); 236 146 237 private static final long serialVersionUID = -4146587895393766981L; 238 239 private LogoutAction() { 240 super(I18n.tr("Logout")); 147 //Restart is never required 148 return false; 241 149 } 242 150 243 151 @Override 244 public void actionPerformed(ActionEvent arg0) { 245 StreetsideUser.reset(); 246 onLogout(); 152 public boolean isExpert() { 153 return false; 247 154 } 248 }249 155 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsideViewerDialog.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside.gui; 3 3 4 import java.awt.BorderLayout; 5 import java.awt.Component; 6 import java.util.List; 4 import java.io.Serial; 7 5 8 import org.openstreetmap.josm.gui.SideButton;9 6 import org.openstreetmap.josm.gui.dialogs.ToggleDialog; 10 7 import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.StreetsideViewerPanel; … … 18 15 public final class StreetsideViewerDialog extends ToggleDialog { 19 16 20 private static final long serialVersionUID = -8983900297628236197L; 17 @Serial 18 private static final long serialVersionUID = 6424974077669812562L; 21 19 22 private static final String BASE_TITLE = "360° Streetside Viewer";20 private static final String BASE_TITLE = "360° Streetside Viewer"; 23 21 24 private static StreetsideViewerDialog instance;22 private static StreetsideViewerDialog instance; 25 23 26 /**27 * Object containing the shown image and that handles zoom and drag28 */29 private final StreetsideViewerPanel streetsideViewerPanel;24 /** 25 * Object containing the shown image and that handles zoom and drag 26 */ 27 private final StreetsideViewerPanel streetsideViewerPanel; 30 28 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 } 37 35 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; 46 46 } 47 return StreetsideViewerDialog.instance;48 }49 47 50 /**51 * @return true, iff the singleton instance is present52 */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 } 56 54 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 } 79 58 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsideWalkDialog.java
r36194 r36228 3 3 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.io.Serial; 5 7 6 8 import javax.swing.JCheckBox; … … 19 21 public class StreetsideWalkDialog extends JPanel { 20 22 21 private static final long serialVersionUID = 7974881240732957573L; 23 @Serial 24 private static final long serialVersionUID = 7974881240732957573L; 22 25 23 /**24 * Spin containing the interval value.25 */26 publicSpinnerModel spin;27 /**28 * Whether it must wait for the picture to be downloaded29 */30 publicJCheckBox waitForPicture;31 /**32 * Whether the view must follow the selected image.33 */34 publicJCheckBox followSelection;35 /**36 * Go forward or backwards37 */38 publicJCheckBox 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; 39 42 40 /**41 * Main constructor42 */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); 49 52 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); 53 56 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); 57 60 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 } 62 65 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/boilerplate/SelectableLabel.java
r36194 r36228 4 4 import java.awt.Color; 5 5 import java.awt.Font; 6 import java.io.Serial; 6 7 7 8 import javax.swing.JTextPane; … … 10 11 public class SelectableLabel extends JTextPane { 11 12 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; 15 17 16 public SelectableLabel() {17 super();18 init();19 }18 public SelectableLabel() { 19 super(); 20 init(); 21 } 20 22 21 public SelectableLabel(String text) {22 this();23 setText(text);24 }23 public SelectableLabel(String text) { 24 this(); 25 setText(text); 26 } 25 27 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 } 33 35 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/boilerplate/StreetsideButton.java
r36194 r36228 6 6 import java.awt.Graphics2D; 7 7 import java.awt.RenderingHints; 8 import java.io.Serial; 8 9 9 10 import javax.swing.Action; … … 15 16 public class StreetsideButton extends JButton { 16 17 17 private static final long serialVersionUID = -3060978712233067368L; 18 @Serial 19 private static final long serialVersionUID = -3060978712233067368L; 18 20 19 public StreetsideButton(final Action action) {20 this(action, false);21 }21 public StreetsideButton(final Action action) { 22 this(action, false); 23 } 22 24 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 } 28 30 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); 39 45 } 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 }44 46 45 @Override46 public boolean isContentAreaFilled() {47 return false;48 }47 @Override 48 public boolean isContentAreaFilled() { 49 return false; 50 } 49 51 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ImageInfoHelpPopup.java
r36194 r36228 7 7 import java.awt.IllegalComponentStateException; 8 8 import java.awt.event.ActionEvent; 9 import java.io.Serial; 9 10 import java.util.logging.Logger; 10 11 … … 25 26 public class ImageInfoHelpPopup extends JPopupMenu { 26 27 27 private static final long serialVersionUID = -1721594904273820586L; 28 @Serial 29 private static final long serialVersionUID = -1721594904273820586L; 28 30 29 private static final Logger LOGGER = Logger.getLogger(ImageInfoHelpPopup.class.getCanonicalName());31 private static final Logger LOGGER = Logger.getLogger(ImageInfoHelpPopup.class.getCanonicalName()); 30 32 31 private final Component invokerComp;32 private boolean alreadyDisplayed;33 private final Component invokerComp; 34 private boolean alreadyDisplayed; 33 35 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()); 38 40 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); 43 45 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); 53 55 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() { 60 62 61 private static final long serialVersionUID = 2853315308169651854L; 63 @Serial 64 private static final long serialVersionUID = 2853315308169651854L; 62 65 63 @Override64 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 }); 69 72 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); 73 76 74 setBackground(Color.WHITE);75 }77 setBackground(Color.WHITE); 78 } 76 79 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; 91 97 } 92 return false;93 }94 98 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ImageInfoPanel.java
r36194 r36228 5 5 import java.awt.GridBagLayout; 6 6 import java.awt.Insets; 7 import java. awt.datatransfer.StringSelection;7 import java.io.Serial; 8 8 import java.util.Collection; 9 9 import java.util.logging.Logger; … … 11 11 import javax.swing.JLabel; 12 12 import javax.swing.JPanel; 13 import javax.swing.JTextPane;14 13 15 14 import org.openstreetmap.josm.data.osm.DataSelectionListener; 16 15 import org.openstreetmap.josm.data.osm.OsmPrimitive; 17 import org.openstreetmap.josm.data.osm.Tag;18 16 import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 19 17 import org.openstreetmap.josm.data.preferences.AbstractProperty.ValueChangeListener; 20 18 import org.openstreetmap.josm.gui.dialogs.ToggleDialog; 21 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;22 19 import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener; 23 20 import org.openstreetmap.josm.plugins.streetside.StreetsideImage; 24 import org.openstreetmap.josm.plugins.streetside.gui.boilerplate.SelectableLabel;25 21 import org.openstreetmap.josm.plugins.streetside.gui.boilerplate.StreetsideButton; 26 22 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties; … … 29 25 import org.openstreetmap.josm.tools.Logging; 30 26 27 /** 28 * A panel for showing image information 29 */ 31 30 public final class ImageInfoPanel extends ToggleDialog implements StreetsideDataListener, DataSelectionListener { 32 private static final long serialVersionUID = 4141847503072417190L; 31 @Serial 32 private static final long serialVersionUID = 1898445061036887054L; 33 33 34 private static final Logger LOGGER = Logger.getLogger(ImageInfoPanel.class.getCanonicalName());34 private static final Logger LOGGER = Logger.getLogger(ImageInfoPanel.class.getCanonicalName()); 35 35 36 private static ImageInfoPanel instance;36 private static ImageInfoPanel instance; 37 37 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; 43 39 44 private ValueChangeListener<Boolean> imageLinkChangeListener;40 private ValueChangeListener<Boolean> imageLinkChangeListener; 45 41 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); 50 46 51 imgKeyValue = new SelectableLabel();47 imgLinkAction = new WebLinkAction(I18n.tr("View in browser"), null); 52 48 53 imgLinkAction = new WebLinkAction(I18n.tr("View in browser"), null);49 final var root = new JPanel(new GridBagLayout()); 54 50 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); 58 53 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); 60 58 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); 62 63 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; 64 68 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); 177 71 } 178 72 179 final boolean partOfSequence = newImage != null && newImage.getSequence() != null180 && 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 } 186 80 } 187 }188 81 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; 198 87 } 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 } 201 130 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/StreetsideViewerPanel.java
r36194 r36228 4 4 import java.awt.BorderLayout; 5 5 import java.awt.GraphicsEnvironment; 6 import java. text.MessageFormat;6 import java.io.Serial; 7 7 import java.util.logging.Logger; 8 import java.util.regex.Pattern; 8 9 9 10 import javax.swing.JCheckBox; … … 12 13 13 14 import org.openstreetmap.josm.data.preferences.AbstractProperty.ValueChangeListener; 14 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;15 15 import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener; 16 16 import org.openstreetmap.josm.plugins.streetside.StreetsideImage; … … 26 26 import org.openstreetmap.josm.tools.Logging; 27 27 28 /** 29 * The panel to view 360 images in 30 */ 28 31 public final class StreetsideViewerPanel extends JPanel implements StreetsideDataListener { 29 32 30 private static final long serialVersionUID = 4141847503072417190L; 33 @Serial 34 private static final long serialVersionUID = 4141847503072417190L; 31 35 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; 39 40 40 public StreetsideViewerPanel() { 41 /** 42 * Create a new 360 viewer 43 */ 44 public StreetsideViewerPanel() { 45 super(new BorderLayout()); 41 46 42 super(new BorderLayout());47 SwingUtilities.invokeLater(this::initializeAndStartGUI); 43 48 44 SwingUtilities.invokeLater(this::initializeAndStartGUI);49 selectedImageChanged(null, null); 45 50 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..")); 69 53 } 70 54 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 } 75 62 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 } 77 70 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; 146 77 } 147 78 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); 154 81 } 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); 157 114 } 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 } 159 159 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ThreeSixtyDegreeViewerPanel.java
r36194 r36228 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.plugins.streetside.gui.imageinfo; 3 4 import java.io.Serial; 3 5 4 6 import org.openstreetmap.josm.plugins.streetside.cubemap.CameraTransformer; … … 14 16 import javafx.scene.control.Label; 15 17 import javafx.scene.control.TextArea; 16 import javafx.scene.image.Image;17 18 import javafx.scene.input.KeyCode; 19 import javafx.scene.input.KeyEvent; 18 20 import javafx.scene.input.MouseEvent; 19 21 import javafx.scene.layout.VBox; … … 22 24 import javafx.scene.text.FontWeight; 23 25 24 @SuppressWarnings("restriction") 26 /** 27 * Create a panel for viewing cube mapped 360 iamges 28 */ 25 29 public class ThreeSixtyDegreeViewerPanel extends JFXPanel { 26 30 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 } 223 200 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/WebLinkAction.java
r36194 r36228 3 3 4 4 import java.awt.event.ActionEvent; 5 import java.io.IOException; 5 import java.io.Serial; 6 import java.net.URISyntaxException; 6 7 import java.net.URL; 7 8 import java.util.logging.Logger; … … 11 12 12 13 import org.openstreetmap.josm.gui.Notification; 13 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideUtils;14 14 import org.openstreetmap.josm.tools.ImageProvider; 15 15 import org.openstreetmap.josm.tools.ImageProvider.ImageSizes; 16 16 import org.openstreetmap.josm.tools.Logging; 17 import org.openstreetmap.josm.tools.OpenBrowser; 17 18 19 /** 20 * Open an image on Microsoft's Streetside website 21 */ 18 22 public class WebLinkAction extends AbstractAction { 19 23 20 private static final long serialVersionUID = -8168227661356480455L; 24 @Serial 25 private static final long serialVersionUID = 6157320554869780625L; 21 26 22 private static final Logger LOGGER = Logger.getLogger(WebLinkAction.class.getCanonicalName());27 private static final Logger LOGGER = Logger.getLogger(WebLinkAction.class.getCanonicalName()); 23 28 24 private URL url;29 private URL url; 25 30 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 } 30 40 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 } 38 49 39 /* (non-Javadoc)40 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)41 */42 @Override43 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 } 50 61 } 51 }52 62 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/BoundsDownloadRunnable.java
r36194 r36228 4 4 import java.awt.GraphicsEnvironment; 5 5 import java.io.IOException; 6 import java.net.HttpURLConnection;7 6 import java.net.URL; 8 7 import java.net.URLConnection; … … 20 19 public abstract class BoundsDownloadRunnable implements Runnable { 21 20 22 private static final Logger LOGGER = Logger.getLogger(BoundsDownloadRunnable.class.getCanonicalName());21 private static final Logger LOGGER = Logger.getLogger(BoundsDownloadRunnable.class.getCanonicalName()); 23 22 24 protectedBounds bounds;23 protected final Bounds bounds; 25 24 26 protected BoundsDownloadRunnable(final Bounds bounds) {27 this.bounds = bounds;28 }25 protected BoundsDownloadRunnable(final Bounds bounds) { 26 this.bounds = bounds; 27 } 29 28 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 } 46 56 } 47 if (info != null && info.length() >= 1) {48 message.append(" (").append(info).append(')');49 }50 LOGGER.info(message::toString);51 }52 57 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; 83 59 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/SequenceDownloadRunnable.java
r36194 r36228 6 6 import java.net.URLConnection; 7 7 import java.text.MessageFormat; 8 import java.time.LocalDate; 9 import java.time.LocalTime; 10 import java.time.ZoneOffset; 11 import java.time.format.DateTimeFormatter; 8 12 import java.util.ArrayList; 9 import java.util.EnumSet;10 13 import java.util.List; 14 import java.util.Objects; 11 15 import java.util.function.Function; 16 import java.util.logging.Level; 12 17 import java.util.logging.Logger; 13 18 14 19 import org.openstreetmap.josm.data.Bounds; 15 import org.openstreetmap.josm.data.coor.LatLon;16 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;17 20 import org.openstreetmap.josm.plugins.streetside.StreetsideData; 18 21 import org.openstreetmap.josm.plugins.streetside.StreetsideImage; 19 import org.openstreetmap.josm.plugins.streetside.StreetsideSequence;20 import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils;21 22 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties; 22 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideSequenceIdGenerator;23 23 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL.APIv3; 24 import org.openstreetmap.josm.tools. Logging;24 import org.openstreetmap.josm.tools.JosmRuntimeException; 25 25 26 26 import jakarta.json.Json; 27 import jakarta.json.JsonException;28 27 import jakarta.json.JsonObject; 29 import jakarta.json.Json Value;28 import jakarta.json.JsonString; 30 29 import jakarta.json.stream.JsonParser; 31 30 31 /** 32 * Download an area 33 */ 32 34 public 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; 36 40 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; 46 49 } 47 50 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 } 49 56 50 List<StreetsideImage> bubbleImages = new ArrayList<>();57 final long startTime = System.currentTimeMillis(); 51 58 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 } 53 65 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}); 130 70 } 131 }132 } catch (ClassCastException | JsonException e) {133 LOG.log(Logging.LEVEL_ERROR, e, () -> MessageFormat134 .format("JSON parsing error occurred during Streetside sequence download {0}", e.getMessage()));135 } catch (IOException e) {136 LOG.log(Logging.LEVEL_ERROR, e, () -> MessageFormat137 .format("Input/output error occurred during Streetside sequence download {0}", e.getMessage()));138 71 } 139 72 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 } 154 84 } 155 }156 } else {157 boolean sequenceCrossesThroughBounds = false;158 for (int i = 0; i < seq.getImages().size() && !sequenceCrossesThroughBounds; i++) {159 sequenceCrossesThroughBounds = bounds.contains(seq.getImages().get(i).getLatLon());160 }161 if (sequenceCrossesThroughBounds) {162 data.addAll(seq.getImages(), true);163 }164 85 } 165 86 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 } 170 98 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 } 175 172 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/StreetsideDownloader.java
r36194 r36228 25 25 public final class StreetsideDownloader { 26 26 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 } 243 261 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/StreetsideSquareDownloadRunnable.java
r36194 r36228 8 8 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideUtils; 9 9 10 /** 11 * Download Streetside images withing a specified bounds 12 */ 10 13 public class StreetsideSquareDownloadRunnable implements Runnable { 11 14 12 private final Bounds bounds;15 private final Bounds bounds; 13 16 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; 33 24 } 34 25 35 // Image detections are not currently supported for Streetside (Mapillary code removed) 26 @Override 27 public void run() { 28 PluginState.startDownload(); 29 StreetsideUtils.updateHelpText(); 36 30 37 PluginState.finishDownload(); 31 // Download basic sequence data synchronously 32 new SequenceDownloadRunnable(StreetsideLayer.getInstance().getData(), bounds).run(); 38 33 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 } 43 46 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/export/StreetsideExportDownloadThread.java
r36194 r36228 16 16 import org.openstreetmap.josm.plugins.streetside.StreetsideImage; 17 17 import org.openstreetmap.josm.plugins.streetside.cache.CacheUtils; 18 import org.openstreetmap.josm.plugins.streetside.cache.StreetsideCache;19 18 import org.openstreetmap.josm.tools.Logging; 20 19 … … 29 28 public class StreetsideExportDownloadThread extends Thread implements ICachedLoaderListener { 30 29 31 private static final Logger LOGGER = Logger.getLogger(StreetsideExportDownloadThread.class.getCanonicalName());30 private static final Logger LOGGER = Logger.getLogger(StreetsideExportDownloadThread.class.getCanonicalName()); 32 31 33 private final ArrayBlockingQueue<BufferedImage> queue;34 private final ArrayBlockingQueue<StreetsideAbstractImage> queueImages;32 private final ArrayBlockingQueue<BufferedImage> queue; 33 private final ArrayBlockingQueue<StreetsideAbstractImage> queueImages; 35 34 36 private final StreetsideImage image;35 private final StreetsideImage image; 37 36 38 /**39 * Main constructor.40 *41 * @param image Image to be downloaded.42 * @param queue Queue of {@link BufferedImage} objects for the43 * {@link StreetsideExportWriterThread}.44 * @param queueImages Queue of {@link StreetsideAbstractImage} objects for the45 * {@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 } 53 52 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 } 59 59 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 } 69 70 } 70 }71 71 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/export/StreetsideExportManager.java
r36194 r36228 33 33 public class StreetsideExportManager extends PleaseWaitRunnable { 34 34 35 private static final Logger LOGGER = Logger.getLogger(StreetsideExportManager.class.getCanonicalName());35 private static final Logger LOGGER = Logger.getLogger(StreetsideExportManager.class.getCanonicalName()); 36 36 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); 39 39 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; 43 43 44 private Thread writer;45 private ThreadPoolExecutor ex;44 private Thread writer; 45 private ThreadPoolExecutor ex; 46 46 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 } 59 59 60 @Override61 protected void cancel() {62 writer.interrupt();63 ex.shutdown();64 }60 @Override 61 protected void cancel() { 62 writer.interrupt(); 63 ex.shutdown(); 64 } 65 65 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 } 78 103 } 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() { 97 107 } 98 try {99 writer.join();100 } catch (InterruptedException e) {101 LOGGER.log(Logging.LEVEL_ERROR, e.getMessage(), e);102 }103 }104 105 @Override106 protected void finish() {107 }108 108 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/export/StreetsideExportWriterThread.java
r36194 r36228 34 34 public class StreetsideExportWriterThread extends Thread { 35 35 36 private static final Logger LOGGER = Logger.getLogger(StreetsideExportWriterThread.class.getCanonicalName());36 private static final Logger LOGGER = Logger.getLogger(StreetsideExportWriterThread.class.getCanonicalName()); 37 37 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; 43 42 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 } 61 59 62 @Override63 public void run() {64 monitor.setCustomText("Downloaded 0/" + amount);65 BufferedImage img;66 StreetsideAbstractImage mimg;67 StringfinalPath = "";68 for (inti = 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(); 72 70 73 // Transforms the image into a byte array.74 ByteArrayOutputStreamoutputStream = 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(); 77 75 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; 83 80 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); 86 108 } 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);114 109 } 115 }116 110 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/mode/AbstractMode.java
r36194 r36228 3 3 4 4 import java.awt.Cursor; 5 import java.awt.Graphics2D;6 5 import java.awt.Point; 7 6 import java.awt.event.MouseAdapter; 8 7 import java.util.Calendar; 9 8 10 import org.openstreetmap.josm.data.Bounds;11 9 import org.openstreetmap.josm.gui.MainApplication; 12 import org.openstreetmap.josm.gui.MapView;13 10 import org.openstreetmap.josm.gui.NavigatableComponent.ZoomChangeListener; 14 import org.openstreetmap.josm.plugins.streetside.Streetside AbstractImage;11 import org.openstreetmap.josm.plugins.streetside.StreetsideImage; 15 12 import org.openstreetmap.josm.plugins.streetside.StreetsideLayer; 16 13 import org.openstreetmap.josm.plugins.streetside.io.download.StreetsideDownloader; … … 24 21 public abstract class AbstractMode extends MouseAdapter implements ZoomChangeListener { 25 22 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(); 77 25 78 26 /** 79 * If in semiautomatic mode, the last Epoch time when there was a download27 * Cursor that should become active when this mode is activated. 80 28 */ 81 p rivate long lastDownload;29 public final int cursor = Cursor.DEFAULT_CURSOR; 82 30 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 } 84 54 85 55 @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(); 93 61 } 94 try {95 Thread.sleep(100);96 } catch (InterruptedException e) {97 return;98 }99 }100 62 } 101 63 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 } 104 93 } 105 }106 94 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/mode/SelectMode.java
r36194 r36228 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.awt.Graphics2D;7 import java.awt.Point;8 import java.awt.event.InputEvent;9 6 import java.awt.event.MouseEvent; 10 import java.util.Objects;11 import java.util.concurrent.ConcurrentSkipListSet;12 7 13 import javax.swing.SwingUtilities;14 15 import org.openstreetmap.josm.data.Bounds;16 import org.openstreetmap.josm.data.coor.LatLon;17 8 import org.openstreetmap.josm.data.osm.OsmPrimitive; 18 9 import org.openstreetmap.josm.gui.MainApplication; 19 import org.openstreetmap.josm.gui.MapView;20 10 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 21 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;22 import org.openstreetmap.josm.plugins.streetside.StreetsideData;23 11 import org.openstreetmap.josm.plugins.streetside.StreetsideImage; 24 12 import org.openstreetmap.josm.plugins.streetside.StreetsideLayer; 25 13 import 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;29 14 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties; 30 15 … … 35 20 */ 36 21 public 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; 42 24 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())); 151 32 } 152 33 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 } 154 46 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 } 160 68 } 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(); 165 80 } 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(); 174 83 } 175 84 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"); 186 88 } 187 188 StreetsideLayer.invalidateInstance();189 }190 191 @Override192 public void paint(Graphics2D g, MapView mv, Bounds box) {193 }194 195 @Override196 public String toString() {197 return tr("Select mode");198 }199 89 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/mode/package-info.java
r34428 r36228 3 3 * The different modes that the {@link org.openstreetmap.josm.plugins.streetside.StreetsideLayer} can be in. 4 4 * <br> 5 * Currently there are two of them:5 * Currently, there are two of them: 6 6 * <ul> 7 7 * <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 5 5 import java.awt.image.AffineTransformOp; 6 6 import java.awt.image.BufferedImage; 7 import java.text.MessageFormat; 7 import java.util.Arrays; 8 import java.util.Map; 9 import java.util.Objects; 8 10 import java.util.logging.Logger; 9 11 12 import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY; 10 13 import org.openstreetmap.josm.tools.Logging; 11 14 12 15 import javafx.application.Platform; 13 import javafx.scene.image.PixelWriter;14 import javafx.scene.image.WritableImage;15 16 16 public class GraphicsUtils { 17 /** 18 * Various graphic utilities, mostly for images. 19 */ 20 public final class GraphicsUtils { 17 21 18 private static final Logger LOGGER = Logger.getLogger(GraphicsUtils.class.getCanonicalName());22 private static final Logger LOGGER = Logger.getLogger(GraphicsUtils.class.getCanonicalName()); 19 23 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 91 26 } 92 27 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); 120 44 } 121 45 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(); 125 54 } 126 55 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) { 129 62 130 public static class PlatformHelper {63 long start = System.currentTimeMillis(); 131 64 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; 134 102 } 135 103 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 } 139 117 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; 144 135 } 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 } 146 160 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/ImageUtil.java
r36194 r36228 7 7 import javax.swing.ImageIcon; 8 8 9 import org.openstreetmap.josm.tools.I18n;10 9 import org.openstreetmap.josm.tools.Logging; 11 10 11 /** 12 * Various utility methods for images 13 */ 12 14 public final class ImageUtil { 13 15 14 private static final Logger LOGGER = Logger.getLogger(ImageUtil.class.getCanonicalName());16 private static final Logger LOGGER = Logger.getLogger(ImageUtil.class.getCanonicalName()); 15 17 16 private ImageUtil() {17 // Private constructor to avoid instantiation18 }18 private ImageUtil() { 19 // Private constructor to avoid instantiation 20 } 19 21 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)); 31 44 } 32 return new ImageIcon(33 icon.getImage()34 .getScaledInstance(35 icon.getIconWidth() >= icon.getIconHeight() ? size36 : Math.max(1,37 Math.round(icon.getIconWidth() / (float) icon.getIconHeight() * size)),38 icon.getIconHeight() >= icon.getIconWidth() ? size39 : Math.max(1,40 Math.round(icon.getIconHeight() / (float) icon.getIconWidth() * size)),41 Image.SCALE_SMOOTH));42 }43 45 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/MapViewGeometryUtil.java
r36194 r36228 7 7 import java.awt.Shape; 8 8 import java.awt.geom.Area; 9 import java.awt.geom.Path2D;10 9 11 10 import org.openstreetmap.josm.data.Bounds; 12 11 import 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;16 12 17 13 /** 18 * Utility class to convert entities like {@link Bounds} and {@link StreetsideSequence}into {@link Shape}s that14 * Utility class to convert entities like {@link Bounds} into {@link Shape}s that 19 15 * can then easily be drawn on a {@link MapView}s {@link Graphics2D}-context. 20 16 */ 21 17 public final class MapViewGeometryUtil { 22 private MapViewGeometryUtil() {23 // Private constructor to avoid instantiation24 }18 private MapViewGeometryUtil() { 19 // Private constructor to avoid instantiation 20 } 25 21 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; 47 45 } 48 return a;49 }50 46 51 /**52 * Converts a {@link StreetsideSequence} into a {@link Path2D} that can be drawn53 * 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 convert57 * @return the {@link Path2D} object to which the {@link StreetsideSequence} has been converted58 */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 }71 47 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/PluginState.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside.utils; 3 3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 4 import java.util.logging.Logger; 7 5 8 import javax.swing.JOptionPane;9 10 import org.openstreetmap.josm.gui.MainApplication;11 6 import org.openstreetmap.josm.tools.I18n; 12 7 import org.openstreetmap.josm.tools.Logging; 13 8 14 9 /** 10 * The current state of the plugin (used for uploads and downloads) 15 11 * @author nokutu 16 *17 12 */ 18 13 public final class PluginState { 19 14 20 private static final Logger LOGGER = Logger.getLogger(PluginState.class.getCanonicalName());15 private static final Logger LOGGER = Logger.getLogger(PluginState.class.getCanonicalName()); 21 16 22 private static boolean submittingChangeset;17 private static int runningDownloads; 23 18 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 } 33 22 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 } 37 29 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 } 44 40 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; 52 48 } 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 }138 49 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/StreetsideColorScheme.java
r36194 r36228 6 6 import javax.swing.JComponent; 7 7 8 /** 9 * The color scheme for Streetside 10 */ 8 11 public final class StreetsideColorScheme { 9 /**10 * Color for unselected images11 */12 public static final Color SEQ_UNSELECTED = new Color(0x00ccd1);13 /**14 * Color for the camera angle indicator of images in unselected sequences15 */16 public static final Color SEQ_UNSELECTED_CA = new Color(0x4169e1);17 /**18 * Color for the marker of images in a selected sequence19 */20 public static final Color SEQ_SELECTED = new Color(0x00b5f5);21 /**22 * Color for the camera angle indicator of images in selected sequences23 */24 public static final Color SEQ_SELECTED_CA = new Color(0x8b008b);25 /**26 * Color for the marker of currently selected images27 */28 public static final Color SEQ_HIGHLIGHTED = new Color(0xf5811a);29 /**30 * Color for the camera angle indicator of the currently selected images31 */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); 33 36 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); 40 39 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 } 43 43 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 } 62 56 } 63 }64 57 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/StreetsideProperties.java
r36194 r36228 10 10 import org.openstreetmap.josm.data.preferences.StringProperty; 11 11 import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.ImageInfoPanel; 12 import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.StreetsideViewerPanel;13 12 import org.openstreetmap.josm.plugins.streetside.io.download.StreetsideDownloader; 14 13 14 /** 15 * Properties for Streetside 16 */ 15 17 public 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); 34 32 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); 48 43 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); 58 53 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); 60 55 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); 67 62 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")); 79 68 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); 87 76 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); 95 82 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 } 111 86 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/StreetsideURL.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside.utils; 3 3 4 import java.io.UnsupportedEncodingException;5 4 import java.net.MalformedURLException; 5 import java.net.URI; 6 import java.net.URISyntaxException; 6 7 import java.net.URL; 7 8 import java.net.URLEncoder; 8 9 import java.nio.charset.StandardCharsets; 9 10 import java.text.MessageFormat; 10 import java.util.ArrayList;11 import java.util.Arrays;12 import java.util.EnumSet;13 11 import java.util.HashMap; 14 import java.util.List;15 12 import java.util.Map; 16 import java.util.Map.Entry;17 13 import java.util.logging.Logger; 18 14 19 15 import org.openstreetmap.josm.data.Bounds; 20 import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils; 21 import org.openstreetmap.josm.tools.I18n; 16 import org.openstreetmap.josm.data.coor.ILatLon; 22 17 import org.openstreetmap.josm.tools.Logging; 23 18 19 /** 20 * A class for building the base URLs for Streetside objects 21 */ 24 22 public final class StreetsideURL { 25 23 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 } 103 76 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 } 345 200 } -
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/StreetsideUtils.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside.utils; 3 3 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;20 4 import org.openstreetmap.josm.gui.MainApplication; 21 import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;22 5 import org.openstreetmap.josm.plugins.streetside.StreetsideLayer; 23 import org.openstreetmap.josm.plugins.streetside.StreetsideSequence;24 6 import org.openstreetmap.josm.tools.I18n; 25 7 … … 31 13 public final class StreetsideUtils { 32 14 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 126 17 } 127 18 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()); 136 38 } 137 138 double result = degMinSec[0].doubleValue(); // degrees139 result += degMinSec[1].doubleValue() / 60; // minutes140 result += degMinSec[2].doubleValue() / 3600; // seconds141 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 merged155 * @param imgB the second image, whose sequence is merged into the sequence of the first image156 */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 the180 * 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 sequence228 * @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 }287 39 } -
applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/StreetsideAbstractImageTest.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside; 3 3 4 import static org.junit.jupiter.api.Assertions.assert False;5 import static org.junit.jupiter.api.Assertions.assert True;4 import static org.junit.jupiter.api.Assertions.assertAll; 5 import static org.junit.jupiter.api.Assertions.assertEquals; 6 6 7 import java.text.MessageFormat; 8 import java.util.stream.Collectors; 9 import java.util.stream.Stream; 10 11 import org.junit.jupiter.api.BeforeEach; 7 12 import org.junit.jupiter.api.Test; 8 import org.openstreetmap.josm.data.coor.LatLon; 13 import org.junit.jupiter.params.ParameterizedTest; 14 import org.junit.jupiter.params.provider.Arguments; 15 import org.junit.jupiter.params.provider.MethodSource; 16 import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils; 17 import org.openstreetmap.josm.plugins.streetside.utils.TestUtil; 9 18 10 19 class 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 11 30 @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")); 27 124 } 28 125 } -
applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/StreetsideDataTest.java
r36194 r36228 12 12 import org.junit.jupiter.api.Disabled; 13 13 import org.junit.jupiter.api.Test; 14 import org.openstreetmap.josm. data.coor.LatLon;14 import org.openstreetmap.josm.plugins.streetside.utils.TestUtil; 15 15 import org.openstreetmap.josm.testutils.annotations.Main; 16 16 … … 31 31 32 32 /** 33 * Creates a sample {@link StreetsideData} object s,4 {@link StreetsideImage}34 * objects and a {@link StreetsideSequence} object.33 * Creates a sample {@link StreetsideData} object and 4 {@link StreetsideImage} 34 * objects. 35 35 */ 36 36 @BeforeEach 37 37 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); 45 42 46 43 data = new StreetsideData(); 47 data.addAll( new ConcurrentSkipListSet<>(seq.getImages()));44 data.addAll(Arrays.asList(img1, img2, img3, img4)); 48 45 } 49 46 … … 72 69 void testSize() { 73 70 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)); 75 72 assertEquals(5, data.getImages().size()); 76 73 } 77 74 78 75 /** 79 * Test the {@link StreetsideData#setHighlightedImage(Streetside AbstractImage)}76 * Test the {@link StreetsideData#setHighlightedImage(StreetsideImage)} 80 77 * and {@link StreetsideData#getHighlightedImage()} methods. 81 78 */ … … 137 134 assertThrows(IllegalStateException.class, data::selectPrevious); 138 135 } 139 140 /**141 * Test the multiselection of images. When a new image is selected, the142 * 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 @Test146 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 }155 136 } -
applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/StreetsideLayerTest.java
r36194 r36228 10 10 import org.junit.jupiter.api.Test; 11 11 import org.junit.jupiter.api.condition.DisabledIf; 12 import org.openstreetmap.josm.data.coor.LatLon;13 12 import org.openstreetmap.josm.data.imagery.ImageryInfo; 14 13 import org.openstreetmap.josm.gui.layer.ImageryLayer; 15 14 import org.openstreetmap.josm.gui.layer.Layer; 16 import org.openstreetmap.josm.plugins.streetside.cubemap.CubemapUtils;17 15 import org.openstreetmap.josm.testutils.JOSMTestRules; 18 16 import org.openstreetmap.josm.testutils.annotations.BasicPreferences; … … 46 44 47 45 @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 @Test69 46 void testGetInfoComponent() { 70 47 Object comp = StreetsideLayer.getInstance().getInfoComponent(); -
applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/cache/StreetsideCacheTest.java
r36194 r36228 4 4 import static org.junit.jupiter.api.Assertions.assertFalse; 5 5 import static org.junit.jupiter.api.Assertions.assertNotNull; 6 import static org.junit.jupiter.api.Assertions.assertNull;7 6 8 7 import org.junit.jupiter.api.Test; 9 import org.openstreetmap.josm.plugins.streetside.cache.StreetsideCache.Type;10 8 import org.openstreetmap.josm.testutils.annotations.BasicPreferences; 11 9 … … 15 13 @Test 16 14 void testCache() { 17 StreetsideCache cache = new StreetsideCache(" 00000", Type.FULL_IMAGE);15 StreetsideCache cache = new StreetsideCache("https://ecn.t0.tiles.virtualearth.net/tiles/hs101320223333223201"); 18 16 assertNotNull(cache.getUrl()); 19 17 assertNotNull(cache.getCacheKey()); … … 21 19 assertFalse(cache.isObjectLoadable()); 22 20 23 cache = new StreetsideCache(" 00000", Type.THUMBNAIL);21 cache = new StreetsideCache("https://ecn.t0.tiles.virtualearth.net/tiles/hs101320223333223201"); 24 22 assertNotNull(cache.getCacheKey()); 25 23 assertNotNull(cache.getUrl()); 26 27 cache = new StreetsideCache(null, null);28 assertNull(cache.getCacheKey());29 assertNull(cache.getUrl());30 24 } 31 25 } -
applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/cubemap/CubemapUtilsTest.java
r36194 r36228 4 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 5 6 import org.junit.jupiter.api.Disabled;7 6 import org.junit.jupiter.api.Test; 8 7 … … 28 27 assertEquals("680931568", res); 29 28 } 30 31 @Disabled32 @Test33 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 @Disabled49 @Test50 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 @Test66 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 }101 29 } -
applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/cubemap/TileDownloadingTaskTest.java
r36194 r36228 7 7 import java.util.List; 8 8 import java.util.concurrent.Callable; 9 import java.util.concurrent.ExecutionException; 9 10 import java.util.concurrent.ExecutorService; 10 11 import java.util.concurrent.Executors; … … 13 14 import org.junit.jupiter.api.Disabled; 14 15 import org.junit.jupiter.api.Test; 16 import org.openstreetmap.josm.plugins.streetside.CubeMapTileXY; 17 import org.openstreetmap.josm.plugins.streetside.utils.TestUtil; 15 18 16 19 @Disabled … … 18 21 19 22 @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 } 26 31 } 27 32 } -
applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/gui/ImageDisplayTest.java
r36194 r36228 25 25 void testImagePersistence() { 26 26 StreetsideImageDisplay display = new StreetsideImageDisplay(); 27 display.setImage(DUMMY_IMAGE , null);27 display.setImage(DUMMY_IMAGE); 28 28 assertEquals(DUMMY_IMAGE, display.getImage()); 29 29 } … … 44 44 display.getMouseWheelListeners()[0].mouseWheelMoved(dummyScroll); 45 45 46 display.setImage(DUMMY_IMAGE , null);46 display.setImage(DUMMY_IMAGE); 47 47 48 48 display.getMouseWheelListeners()[0].mouseWheelMoved(dummyScroll); … … 72 72 display.getMouseListeners()[0].mouseClicked(dummyClick); 73 73 74 display.setImage(DUMMY_IMAGE , null);74 display.setImage(DUMMY_IMAGE); 75 75 76 76 display.getMouseListeners()[0].mouseClicked(dummyClick); -
applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/gui/StreetsidePreferenceSettingTest.java
r36194 r36228 6 6 7 7 import java.awt.GraphicsEnvironment; 8 import java.util.Objects; 8 9 9 10 import javax.swing.JCheckBox; … … 99 100 settings.ok(); 100 101 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()); 103 104 } 104 105 } 105 106 106 107 /** 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} 108 110 * @param cb the {@link JCheckBox}, which should be checked against the {@link BooleanProperty} 109 111 * @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 3 3 4 4 import 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;10 5 11 6 import org.junit.jupiter.api.Disabled; … … 20 15 class SequenceDownloadRunnableTest { 21 16 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 27 17 @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)); 30 20 } 31 21 32 22 @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)); 35 25 } 36 26 37 27 @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)); 46 30 } 47 31 48 32 @Test 49 void testRun4() throws IllegalArgumentException , IllegalAccessException{33 void testRun4() throws IllegalArgumentException { 50 34 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)); 52 36 } 53 37 54 38 @Test 55 void testRun5() throws IllegalArgumentException , IllegalAccessException{39 void testRun5() throws IllegalArgumentException { 56 40 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)); 58 42 } 59 43 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 { 62 46 SequenceDownloadRunnable r = new SequenceDownloadRunnable(StreetsideLayer.getInstance().getData(), bounds); 63 urlGenField.set(null, urlGen);64 47 r.run(); 65 48 assertEquals(expectedNumImgs, StreetsideLayer.getInstance().getData().getImages().size()); -
applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/utils/PluginStateTest.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside.utils; 3 3 4 import static org.junit.jupiter.api.Assertions.assertEquals;5 4 import static org.junit.jupiter.api.Assertions.assertFalse; 6 5 import static org.junit.jupiter.api.Assertions.assertTrue; 7 6 8 import javax.swing.JOptionPane;9 10 7 import org.junit.jupiter.api.Test; 11 import org.openstreetmap.josm.TestUtils;12 import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker;13 8 14 9 /** … … 35 30 assertFalse(PluginState.isDownloading()); 36 31 } 37 38 /**39 * Tests the methods related to the upload.40 */41 @Test42 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 }59 32 } -
applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/utils/StreetsideURLTest.java
r36194 r36228 5 5 import static org.junit.jupiter.api.Assertions.assertNull; 6 6 import static org.junit.jupiter.api.Assertions.assertThrows; 7 import static org.junit.jupiter.api.Assertions.fail; 7 8 8 9 import java.lang.reflect.InvocationTargetException; 9 10 import java.lang.reflect.Method; 10 11 import java.net.MalformedURLException; 11 import java.net.URL; 12 import java.net.URI; 13 import java.net.URISyntaxException; 12 14 13 15 import org.junit.jupiter.api.Assertions; … … 16 18 17 19 class 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)); 43 31 } 44 32 45 33 @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 { 57 35 String headerVal = "<https://urlFirst>; rel=\"first\", " + "rel = \"next\" ; < ; , " 58 36 + "rel = \"next\" ; <https://urlNext> , " + "<https://urlPrev>; rel=\"prev\""; 59 assertEquals(new UR L("https://urlNext"), StreetsideURL.APIv3.parseNextFromLinkHeaderValue(headerVal));37 assertEquals(new URI("https://urlNext").toURL(), StreetsideURL.APIv3.parseNextFromLinkHeaderValue(headerVal)); 60 38 } 61 39 … … 70 48 } 71 49 72 /*public static class Cloudfront {73 @Ignore74 @Test75 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 81 50 @Disabled 82 51 @Test 83 52 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")); 86 56 } 87 57 … … 91 61 } 92 62 93 @Disabled94 @Test95 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 @Disabled123 @Test124 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 @Disabled132 @Test133 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 140 63 @Test 141 64 void testString2MalformedURL() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, … … 143 66 Method method = StreetsideURL.class.getDeclaredMethod("string2URL", String[].class); 144 67 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)); 147 72 } 148 73 … … 151 76 TestUtil.testUtilityClass(StreetsideURL.class); 152 77 TestUtil.testUtilityClass(StreetsideURL.APIv3.class); 153 TestUtil.testUtilityClass(StreetsideURL.VirtualEarth.class);154 78 TestUtil.testUtilityClass(StreetsideURL.MainWebsite.class); 155 79 } 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 }173 80 } -
applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/utils/StreetsideUtilsTest.java
r36194 r36228 2 2 package org.openstreetmap.josm.plugins.streetside.utils; 3 3 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;8 4 import org.junit.jupiter.api.Test; 9 5 … … 21 17 } 22 18 23 /**24 * Test {@link StreetsideUtils#degMinSecToDouble(RationalNumber[], String)}25 * method.26 */27 @Test28 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 }42 19 } -
applications/editors/josm/plugins/MicrosoftStreetside/test/unit/org/openstreetmap/josm/plugins/streetside/utils/TestUtil.java
r36194 r36228 10 10 import java.lang.reflect.Method; 11 11 import java.lang.reflect.Modifier; 12 import java.time.Instant; 13 import java.util.Arrays; 12 14 15 import org.openstreetmap.josm.plugins.streetside.StreetsideImage; 13 16 import org.openstreetmap.josm.tools.JosmRuntimeException; 14 17 … … 76 79 } 77 80 } 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 } 78 97 } -
applications/editors/josm/plugins/build.xml
r36190 r36228 14 14 <property name="java21_plugins" value="FIT/build.xml" /> 15 15 <property name="java17_plugins" value="maproulette/build.xml 16 MicrosoftStreetside/build.xml 16 17 imageio/build.xml 17 18 pmtiles/build.xml … … 32 33 apache-http/build.xml 33 34 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"/> 36 36 37 37 <!-- 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.