001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.imagery; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.util.ArrayList; 007import java.util.Collection; 008import java.util.Collections; 009import java.util.List; 010import java.util.ListIterator; 011import java.util.Map; 012 013import org.openstreetmap.josm.Main; 014import org.openstreetmap.josm.data.StructUtils; 015import org.openstreetmap.josm.data.StructUtils.StructEntry; 016import org.openstreetmap.josm.data.StructUtils.WriteExplicitly; 017import org.openstreetmap.josm.data.coor.EastNorth; 018import org.openstreetmap.josm.data.coor.ILatLon; 019import org.openstreetmap.josm.data.coor.LatLon; 020import org.openstreetmap.josm.data.projection.Projection; 021import org.openstreetmap.josm.data.projection.Projections; 022import org.openstreetmap.josm.gui.MainApplication; 023import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer; 024import org.openstreetmap.josm.gui.layer.ImageryLayer; 025import org.openstreetmap.josm.spi.preferences.Config; 026import org.openstreetmap.josm.tools.Logging; 027 028/** 029 * Class to save a displacement of background imagery as a bookmark. 030 * 031 * Known offset bookmarks will be stored in the preferences and can be 032 * restored by the user in later sessions. 033 */ 034public class OffsetBookmark { 035 private static final List<OffsetBookmark> allBookmarks = new ArrayList<>(); 036 037 @StructEntry private String projection_code; 038 @StructEntry private String imagery_name; 039 @StructEntry private String name; 040 @StructEntry @WriteExplicitly private double dx, dy; 041 @StructEntry private double center_lon, center_lat; 042 043 /** 044 * Test if an image is usable for the given imagery layer. 045 * @param layer The layer to use the image at 046 * @return <code>true</code> if it is usable on the projection of the layer and the imagery name matches. 047 */ 048 public boolean isUsable(ImageryLayer layer) { 049 if (projection_code == null) return false; 050 if (!Main.getProjection().toCode().equals(projection_code) && !hasCenter()) return false; 051 return layer.getInfo().getName().equals(imagery_name); 052 } 053 054 /** 055 * Construct new empty OffsetBookmark. 056 * 057 * Only used for preferences handling. 058 */ 059 public OffsetBookmark() { 060 // do nothing 061 } 062 063 /** 064 * Create a new {@link OffsetBookmark} object using (0, 0) as center 065 * <p> 066 * The use of the {@link #OffsetBookmark(String, String, String, EastNorth, ILatLon)} constructor is preferred. 067 * @param projectionCode The projection for which this object was created 068 * @param imageryName The name of the imagery on the layer 069 * @param name The name of the new bookmark 070 * @param dx The x displacement 071 * @param dy The y displacement 072 */ 073 public OffsetBookmark(String projectionCode, String imageryName, String name, double dx, double dy) { 074 this(projectionCode, imageryName, name, dx, dy, 0, 0); 075 } 076 077 /** 078 * Create a new {@link OffsetBookmark} object 079 * @param projectionCode The projection for which this object was created 080 * @param imageryName The name of the imagery on the layer 081 * @param name The name of the new bookmark 082 * @param displacement The displacement in east/north space. 083 * @param center The point on earth that was used as reference to align the image. 084 * @since 13243 085 */ 086 public OffsetBookmark(String projectionCode, String imageryName, String name, EastNorth displacement, ILatLon center) { 087 this(projectionCode, imageryName, name, displacement.east(), displacement.north(), center.lon(), center.lat()); 088 } 089 090 /** 091 * Create a new {@link OffsetBookmark} by specifying all values. 092 * <p> 093 * The use of the {@link #OffsetBookmark(String, String, String, EastNorth, ILatLon)} constructor is preferred. 094 * @param projectionCode The projection for which this object was created 095 * @param imageryName The name of the imagery on the layer 096 * @param name The name of the new bookmark 097 * @param dx The x displacement 098 * @param dy The y displacement 099 * @param centerLon The point on earth that was used as reference to align the image. 100 * @param centerLat The point on earth that was used as reference to align the image. 101 */ 102 public OffsetBookmark(String projectionCode, String imageryName, String name, double dx, double dy, double centerLon, double centerLat) { 103 this.projection_code = projectionCode; 104 this.imagery_name = imageryName; 105 this.name = name; 106 this.dx = dx; 107 this.dy = dy; 108 this.center_lon = centerLon; 109 this.center_lat = centerLat; 110 } 111 112 /** 113 * Loads an old bookmark. For backward compatibility with settings. Do not use. 114 * @param list The settings that were read 115 */ 116 public OffsetBookmark(Collection<String> list) { 117 List<String> array = new ArrayList<>(list); 118 this.projection_code = array.get(0); 119 this.imagery_name = array.get(1); 120 this.name = array.get(2); 121 this.dx = Double.parseDouble(array.get(3)); 122 this.dy = Double.parseDouble(array.get(4)); 123 if (array.size() >= 7) { 124 this.center_lon = Double.parseDouble(array.get(5)); 125 this.center_lat = Double.parseDouble(array.get(6)); 126 } 127 if (projection_code == null) { 128 Logging.error(tr("Projection ''{0}'' is not found, bookmark ''{1}'' is not usable", projection_code, name)); 129 } 130 } 131 132 /** 133 * Get the projection code for which this bookmark was created. 134 * @return The projection. 135 */ 136 public String getProjectionCode() { 137 return projection_code; 138 } 139 140 /** 141 * Get the name of this bookmark. This name can e.g. be displayed in menus. 142 * @return The name 143 */ 144 public String getName() { 145 return name; 146 } 147 148 /** 149 * Get the name of the imagery for which this bookmark was created. It is used to match the bookmark to the right layers. 150 * @return The name 151 */ 152 public String getImageryName() { 153 return imagery_name; 154 } 155 156 /** 157 * Get displacement in EastNorth coordinates of the original projection. 158 * 159 * @return the displacement 160 * @see #getProjectionCode() 161 */ 162 public EastNorth getDisplacement() { 163 return new EastNorth(dx, dy); 164 } 165 166 /** 167 * Get displacement in EastNorth coordinates of a given projection. 168 * 169 * Displacement will be converted to the given projection, with respect to the 170 * center (reference point) of this bookmark. 171 * @param proj the projection 172 * @return the displacement, converted to that projection 173 */ 174 public EastNorth getDisplacement(Projection proj) { 175 if (proj.toCode().equals(projection_code)) { 176 return getDisplacement(); 177 } 178 LatLon center = getCenter(); 179 Projection offsetProj = Projections.getProjectionByCode(projection_code); 180 EastNorth centerEN = center.getEastNorth(offsetProj); 181 EastNorth shiftedEN = centerEN.add(getDisplacement()); 182 LatLon shifted = offsetProj.eastNorth2latlon(shiftedEN); 183 EastNorth centerEN2 = center.getEastNorth(proj); 184 EastNorth shiftedEN2 = shifted.getEastNorth(proj); 185 return shiftedEN2.subtract(centerEN2); 186 } 187 188 /** 189 * Get center/reference point of the bookmark. 190 * 191 * Basically this is the place where it was created and is valid. 192 * The center may be unrecorded (see {@link #hasCenter()}, in which 193 * case a dummy center (0,0) will be returned. 194 * @return the center 195 */ 196 public LatLon getCenter() { 197 return new LatLon(center_lat, center_lon); 198 } 199 200 /** 201 * Check if bookmark has a valid center. 202 * @return true if bookmark has a valid center 203 */ 204 public boolean hasCenter() { 205 return center_lat != 0 || center_lon != 0; 206 } 207 208 /** 209 * Set the projection code for which this bookmark was created 210 * @param projectionCode The projection 211 */ 212 public void setProjectionCode(String projectionCode) { 213 this.projection_code = projectionCode; 214 } 215 216 /** 217 * Set the name of the bookmark 218 * @param name The name 219 * @see #getName() 220 */ 221 public void setName(String name) { 222 this.name = name; 223 } 224 225 /** 226 * Sets the name of the imagery 227 * @param imageryName The name 228 * @see #getImageryName() 229 */ 230 public void setImageryName(String imageryName) { 231 this.imagery_name = imageryName; 232 } 233 234 /** 235 * Update the displacement of this imagery. 236 * @param displacement The displacement 237 */ 238 public void setDisplacement(EastNorth displacement) { 239 this.dx = displacement.east(); 240 this.dy = displacement.north(); 241 } 242 243 /** 244 * Load the global list of bookmarks from preferences. 245 */ 246 public static void loadBookmarks() { 247 List<OffsetBookmark> bookmarks = StructUtils.getListOfStructs( 248 Config.getPref(), "imagery.offsetbookmarks", null, OffsetBookmark.class); 249 if (bookmarks == null) { 250 loadBookmarksOld(); 251 saveBookmarks(); 252 } else { 253 allBookmarks.addAll(bookmarks); 254 } 255 } 256 257 // migration code - remove Nov. 2017 258 private static void loadBookmarksOld() { 259 for (Collection<String> c : Config.getPref().getListOfLists("imagery.offsets")) { 260 allBookmarks.add(new OffsetBookmark(c)); 261 } 262 } 263 264 /** 265 * Stores the bookmakrs in the settings. 266 */ 267 public static void saveBookmarks() { 268 StructUtils.putListOfStructs(Config.getPref(), "imagery.offsetbookmarks", allBookmarks, OffsetBookmark.class); 269 } 270 271 /** 272 * Returns all bookmarks. 273 * @return all bookmarks (unmodifiable collection) 274 * @since 11651 275 */ 276 public static List<OffsetBookmark> getBookmarks() { 277 return Collections.unmodifiableList(allBookmarks); 278 } 279 280 /** 281 * Returns the number of bookmarks. 282 * @return the number of bookmarks 283 * @since 11651 284 */ 285 public static int getBookmarksSize() { 286 return allBookmarks.size(); 287 } 288 289 /** 290 * Adds a bookmark. 291 * @param ob bookmark to add 292 * @return {@code true} 293 * @since 11651 294 */ 295 public static boolean addBookmark(OffsetBookmark ob) { 296 return allBookmarks.add(ob); 297 } 298 299 /** 300 * Removes a bookmark. 301 * @param ob bookmark to remove 302 * @return {@code true} if this list contained the specified element 303 * @since 11651 304 */ 305 public static boolean removeBookmark(OffsetBookmark ob) { 306 return allBookmarks.remove(ob); 307 } 308 309 /** 310 * Returns the bookmark at the given index. 311 * @param index bookmark index 312 * @return the bookmark at the given index 313 * @throws IndexOutOfBoundsException if the index is out of range 314 * (<code>index < 0 || index >= size()</code>) 315 * @since 11651 316 */ 317 public static OffsetBookmark getBookmarkByIndex(int index) { 318 return allBookmarks.get(index); 319 } 320 321 /** 322 * Gets a bookmark that is usable on the given layer by it's name. 323 * @param layer The layer to use the bookmark at 324 * @param name The name of the bookmark 325 * @return The bookmark if found, <code>null</code> if not. 326 */ 327 public static OffsetBookmark getBookmarkByName(ImageryLayer layer, String name) { 328 for (OffsetBookmark b : allBookmarks) { 329 if (b.isUsable(layer) && name.equals(b.name)) 330 return b; 331 } 332 return null; 333 } 334 335 /** 336 * Add a bookmark for the displacement of that layer 337 * @param name The bookmark name 338 * @param layer The layer to store the bookmark for 339 */ 340 public static void bookmarkOffset(String name, AbstractTileSourceLayer<?> layer) { 341 LatLon center; 342 if (MainApplication.isDisplayingMapView()) { 343 center = Main.getProjection().eastNorth2latlon(MainApplication.getMap().mapView.getCenter()); 344 } else { 345 center = LatLon.ZERO; 346 } 347 OffsetBookmark nb = new OffsetBookmark( 348 Main.getProjection().toCode(), layer.getInfo().getName(), 349 name, layer.getDisplaySettings().getDisplacement(), center); 350 for (ListIterator<OffsetBookmark> it = allBookmarks.listIterator(); it.hasNext();) { 351 OffsetBookmark b = it.next(); 352 if (b.isUsable(layer) && name.equals(b.name)) { 353 it.set(nb); 354 saveBookmarks(); 355 return; 356 } 357 } 358 allBookmarks.add(nb); 359 saveBookmarks(); 360 } 361 362 /** 363 * Converts the offset bookmark to a properties map. 364 * 365 * The map contains all the information to restore the offset bookmark. 366 * @return properties map of all data 367 * @see #fromPropertiesMap(java.util.Map) 368 * @since 12134 369 */ 370 public Map<String, String> toPropertiesMap() { 371 return StructUtils.serializeStruct(this, OffsetBookmark.class); 372 } 373 374 /** 375 * Creates an offset bookmark from a properties map. 376 * @param properties the properties map 377 * @return corresponding offset bookmark 378 * @see #toPropertiesMap() 379 * @since 12134 380 */ 381 public static OffsetBookmark fromPropertiesMap(Map<String, String> properties) { 382 return StructUtils.deserializeStruct(properties, OffsetBookmark.class); 383 } 384}