[3719] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[3715] | 2 | package org.openstreetmap.josm.data.imagery;
|
---|
| 3 |
|
---|
| 4 | import java.util.ArrayList;
|
---|
| 5 | import java.util.Collections;
|
---|
| 6 | import java.util.List;
|
---|
[3734] | 7 | import java.util.ListIterator;
|
---|
[12134] | 8 | import java.util.Map;
|
---|
[13797] | 9 | import java.util.Objects;
|
---|
| 10 | import java.util.stream.Collectors;
|
---|
[3715] | 11 |
|
---|
| 12 | import org.openstreetmap.josm.Main;
|
---|
[12851] | 13 | import org.openstreetmap.josm.data.StructUtils;
|
---|
| 14 | import org.openstreetmap.josm.data.StructUtils.StructEntry;
|
---|
| 15 | import org.openstreetmap.josm.data.StructUtils.WriteExplicitly;
|
---|
[12084] | 16 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
[13243] | 17 | import org.openstreetmap.josm.data.coor.ILatLon;
|
---|
[3775] | 18 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
[12093] | 19 | import org.openstreetmap.josm.data.projection.Projection;
|
---|
| 20 | import org.openstreetmap.josm.data.projection.Projections;
|
---|
[12630] | 21 | import org.openstreetmap.josm.gui.MainApplication;
|
---|
[10742] | 22 | import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer;
|
---|
[3715] | 23 | import org.openstreetmap.josm.gui.layer.ImageryLayer;
|
---|
[12846] | 24 | import org.openstreetmap.josm.spi.preferences.Config;
|
---|
[12620] | 25 | import org.openstreetmap.josm.tools.Logging;
|
---|
[3715] | 26 |
|
---|
[12093] | 27 | /**
|
---|
| 28 | * Class to save a displacement of background imagery as a bookmark.
|
---|
| 29 | *
|
---|
| 30 | * Known offset bookmarks will be stored in the preferences and can be
|
---|
| 31 | * restored by the user in later sessions.
|
---|
| 32 | */
|
---|
[3715] | 33 | public class OffsetBookmark {
|
---|
[11651] | 34 | private static final List<OffsetBookmark> allBookmarks = new ArrayList<>();
|
---|
[3715] | 35 |
|
---|
[12851] | 36 | @StructEntry private String projection_code;
|
---|
[13797] | 37 | @StructEntry private String imagery_id;
|
---|
| 38 | /** Imagery localized name. Locale insensitive {@link #imagery_id} is preferred. */
|
---|
[12851] | 39 | @StructEntry private String imagery_name;
|
---|
| 40 | @StructEntry private String name;
|
---|
| 41 | @StructEntry @WriteExplicitly private double dx, dy;
|
---|
| 42 | @StructEntry private double center_lon, center_lat;
|
---|
[3715] | 43 |
|
---|
[13243] | 44 | /**
|
---|
| 45 | * Test if an image is usable for the given imagery layer.
|
---|
| 46 | * @param layer The layer to use the image at
|
---|
| 47 | * @return <code>true</code> if it is usable on the projection of the layer and the imagery name matches.
|
---|
| 48 | */
|
---|
[3715] | 49 | public boolean isUsable(ImageryLayer layer) {
|
---|
[12084] | 50 | if (projection_code == null) return false;
|
---|
[12093] | 51 | if (!Main.getProjection().toCode().equals(projection_code) && !hasCenter()) return false;
|
---|
[13797] | 52 | ImageryInfo info = layer.getInfo();
|
---|
| 53 | return imagery_id != null ? Objects.equals(info.getId(), imagery_id) : Objects.equals(info.getName(), imagery_name);
|
---|
[3715] | 54 | }
|
---|
[3775] | 55 |
|
---|
[12084] | 56 | /**
|
---|
| 57 | * Construct new empty OffsetBookmark.
|
---|
| 58 | *
|
---|
| 59 | * Only used for preferences handling.
|
---|
| 60 | */
|
---|
| 61 | public OffsetBookmark() {
|
---|
| 62 | // do nothing
|
---|
[3775] | 63 | }
|
---|
| 64 |
|
---|
[13243] | 65 | /**
|
---|
| 66 | * Create a new {@link OffsetBookmark} object using (0, 0) as center
|
---|
| 67 | * <p>
|
---|
[13797] | 68 | * The use of the {@link #OffsetBookmark(String, String, String, String, EastNorth, ILatLon)} constructor is preferred.
|
---|
[13243] | 69 | * @param projectionCode The projection for which this object was created
|
---|
[13797] | 70 | * @param imageryId The id of the imagery on the layer (locale insensitive)
|
---|
| 71 | * @param imageryName The name of the imagery on the layer (locale sensitive)
|
---|
[13243] | 72 | * @param name The name of the new bookmark
|
---|
| 73 | * @param dx The x displacement
|
---|
| 74 | * @param dy The y displacement
|
---|
[13797] | 75 | * @since 13797
|
---|
[13243] | 76 | */
|
---|
[13797] | 77 | public OffsetBookmark(String projectionCode, String imageryId, String imageryName, String name, double dx, double dy) {
|
---|
| 78 | this(projectionCode, imageryId, imageryName, name, dx, dy, 0, 0);
|
---|
[12084] | 79 | }
|
---|
| 80 |
|
---|
[13243] | 81 | /**
|
---|
| 82 | * Create a new {@link OffsetBookmark} object
|
---|
| 83 | * @param projectionCode The projection for which this object was created
|
---|
[13797] | 84 | * @param imageryId The id of the imagery on the layer (locale insensitive)
|
---|
| 85 | * @param imageryName The name of the imagery on the layer (locale sensitive)
|
---|
[13243] | 86 | * @param name The name of the new bookmark
|
---|
| 87 | * @param displacement The displacement in east/north space.
|
---|
| 88 | * @param center The point on earth that was used as reference to align the image.
|
---|
[13797] | 89 | * @since 13797
|
---|
[13243] | 90 | */
|
---|
[13797] | 91 | public OffsetBookmark(String projectionCode, String imageryId, String imageryName, String name, EastNorth displacement, ILatLon center) {
|
---|
| 92 | this(projectionCode, imageryId, imageryName, name, displacement.east(), displacement.north(), center.lon(), center.lat());
|
---|
[13243] | 93 | }
|
---|
| 94 |
|
---|
| 95 | /**
|
---|
| 96 | * Create a new {@link OffsetBookmark} by specifying all values.
|
---|
| 97 | * <p>
|
---|
[13797] | 98 | * The use of the {@link #OffsetBookmark(String, String, String, String, EastNorth, ILatLon)} constructor is preferred.
|
---|
[13243] | 99 | * @param projectionCode The projection for which this object was created
|
---|
[13797] | 100 | * @param imageryId The id of the imagery on the layer (locale insensitive)
|
---|
| 101 | * @param imageryName The name of the imagery on the layer (locale sensitive)
|
---|
[13243] | 102 | * @param name The name of the new bookmark
|
---|
| 103 | * @param dx The x displacement
|
---|
| 104 | * @param dy The y displacement
|
---|
| 105 | * @param centerLon The point on earth that was used as reference to align the image.
|
---|
| 106 | * @param centerLat The point on earth that was used as reference to align the image.
|
---|
[13797] | 107 | * @since 13797
|
---|
[13243] | 108 | */
|
---|
[13797] | 109 | public OffsetBookmark(String projectionCode, String imageryId, String imageryName, String name,
|
---|
| 110 | double dx, double dy, double centerLon, double centerLat) {
|
---|
[12084] | 111 | this.projection_code = projectionCode;
|
---|
[13797] | 112 | this.imagery_id = imageryId;
|
---|
[12084] | 113 | this.imagery_name = imageryName;
|
---|
[3715] | 114 | this.name = name;
|
---|
| 115 | this.dx = dx;
|
---|
| 116 | this.dy = dy;
|
---|
[12084] | 117 | this.center_lon = centerLon;
|
---|
| 118 | this.center_lat = centerLat;
|
---|
[3715] | 119 | }
|
---|
| 120 |
|
---|
[13243] | 121 | /**
|
---|
| 122 | * Get the projection code for which this bookmark was created.
|
---|
| 123 | * @return The projection.
|
---|
| 124 | */
|
---|
[12084] | 125 | public String getProjectionCode() {
|
---|
| 126 | return projection_code;
|
---|
| 127 | }
|
---|
| 128 |
|
---|
[13243] | 129 | /**
|
---|
| 130 | * Get the name of this bookmark. This name can e.g. be displayed in menus.
|
---|
| 131 | * @return The name
|
---|
| 132 | */
|
---|
[12084] | 133 | public String getName() {
|
---|
| 134 | return name;
|
---|
| 135 | }
|
---|
| 136 |
|
---|
[13243] | 137 | /**
|
---|
[13797] | 138 | * Get the id of the imagery for which this bookmark was created. It is used to match the bookmark to the right layers.
|
---|
| 139 | * @return The imagery identifier
|
---|
| 140 | * @since 13797
|
---|
| 141 | */
|
---|
| 142 | public String getImageryId() {
|
---|
| 143 | return imagery_id;
|
---|
| 144 | }
|
---|
| 145 |
|
---|
| 146 | /**
|
---|
| 147 | * Get the name of the imagery for which this bookmark was created.
|
---|
| 148 | * It is used to match the bookmark to the right layers if id is missing.
|
---|
[13243] | 149 | * @return The name
|
---|
| 150 | */
|
---|
[12084] | 151 | public String getImageryName() {
|
---|
| 152 | return imagery_name;
|
---|
| 153 | }
|
---|
| 154 |
|
---|
[12093] | 155 | /**
|
---|
| 156 | * Get displacement in EastNorth coordinates of the original projection.
|
---|
| 157 | *
|
---|
[12118] | 158 | * @return the displacement
|
---|
[12093] | 159 | * @see #getProjectionCode()
|
---|
| 160 | */
|
---|
| 161 | public EastNorth getDisplacement() {
|
---|
[12084] | 162 | return new EastNorth(dx, dy);
|
---|
| 163 | }
|
---|
| 164 |
|
---|
[12093] | 165 | /**
|
---|
| 166 | * Get displacement in EastNorth coordinates of a given projection.
|
---|
| 167 | *
|
---|
| 168 | * Displacement will be converted to the given projection, with respect to the
|
---|
| 169 | * center (reference point) of this bookmark.
|
---|
| 170 | * @param proj the projection
|
---|
| 171 | * @return the displacement, converted to that projection
|
---|
| 172 | */
|
---|
| 173 | public EastNorth getDisplacement(Projection proj) {
|
---|
| 174 | if (proj.toCode().equals(projection_code)) {
|
---|
| 175 | return getDisplacement();
|
---|
| 176 | }
|
---|
| 177 | LatLon center = getCenter();
|
---|
| 178 | Projection offsetProj = Projections.getProjectionByCode(projection_code);
|
---|
[12163] | 179 | EastNorth centerEN = center.getEastNorth(offsetProj);
|
---|
[12093] | 180 | EastNorth shiftedEN = centerEN.add(getDisplacement());
|
---|
| 181 | LatLon shifted = offsetProj.eastNorth2latlon(shiftedEN);
|
---|
[12163] | 182 | EastNorth centerEN2 = center.getEastNorth(proj);
|
---|
| 183 | EastNorth shiftedEN2 = shifted.getEastNorth(proj);
|
---|
[12093] | 184 | return shiftedEN2.subtract(centerEN2);
|
---|
| 185 | }
|
---|
| 186 |
|
---|
| 187 | /**
|
---|
| 188 | * Get center/reference point of the bookmark.
|
---|
| 189 | *
|
---|
| 190 | * Basically this is the place where it was created and is valid.
|
---|
[12118] | 191 | * The center may be unrecorded (see {@link #hasCenter()}, in which
|
---|
[12093] | 192 | * case a dummy center (0,0) will be returned.
|
---|
| 193 | * @return the center
|
---|
| 194 | */
|
---|
[12084] | 195 | public LatLon getCenter() {
|
---|
| 196 | return new LatLon(center_lat, center_lon);
|
---|
| 197 | }
|
---|
| 198 |
|
---|
[12093] | 199 | /**
|
---|
| 200 | * Check if bookmark has a valid center.
|
---|
| 201 | * @return true if bookmark has a valid center
|
---|
| 202 | */
|
---|
| 203 | public boolean hasCenter() {
|
---|
| 204 | return center_lat != 0 || center_lon != 0;
|
---|
| 205 | }
|
---|
| 206 |
|
---|
[13243] | 207 | /**
|
---|
| 208 | * Set the projection code for which this bookmark was created
|
---|
| 209 | * @param projectionCode The projection
|
---|
| 210 | */
|
---|
[12084] | 211 | public void setProjectionCode(String projectionCode) {
|
---|
| 212 | this.projection_code = projectionCode;
|
---|
| 213 | }
|
---|
| 214 |
|
---|
[13243] | 215 | /**
|
---|
| 216 | * Set the name of the bookmark
|
---|
| 217 | * @param name The name
|
---|
| 218 | * @see #getName()
|
---|
| 219 | */
|
---|
[12084] | 220 | public void setName(String name) {
|
---|
| 221 | this.name = name;
|
---|
| 222 | }
|
---|
| 223 |
|
---|
[13243] | 224 | /**
|
---|
| 225 | * Sets the name of the imagery
|
---|
| 226 | * @param imageryName The name
|
---|
| 227 | * @see #getImageryName()
|
---|
| 228 | */
|
---|
[12084] | 229 | public void setImageryName(String imageryName) {
|
---|
| 230 | this.imagery_name = imageryName;
|
---|
| 231 | }
|
---|
| 232 |
|
---|
[13243] | 233 | /**
|
---|
[13797] | 234 | * Sets the id of the imagery
|
---|
| 235 | * @param imageryId The identifier
|
---|
| 236 | * @see #getImageryId()
|
---|
[13798] | 237 | * @since 13797
|
---|
[13797] | 238 | */
|
---|
| 239 | public void setImageryId(String imageryId) {
|
---|
| 240 | this.imagery_id = imageryId;
|
---|
| 241 | }
|
---|
| 242 |
|
---|
| 243 | /**
|
---|
[13243] | 244 | * Update the displacement of this imagery.
|
---|
| 245 | * @param displacement The displacement
|
---|
| 246 | */
|
---|
[12093] | 247 | public void setDisplacement(EastNorth displacement) {
|
---|
| 248 | this.dx = displacement.east();
|
---|
| 249 | this.dy = displacement.north();
|
---|
[12084] | 250 | }
|
---|
| 251 |
|
---|
[13243] | 252 | /**
|
---|
| 253 | * Load the global list of bookmarks from preferences.
|
---|
| 254 | */
|
---|
[12084] | 255 | public static void loadBookmarks() {
|
---|
[12851] | 256 | List<OffsetBookmark> bookmarks = StructUtils.getListOfStructs(
|
---|
| 257 | Config.getPref(), "imagery.offsetbookmarks", null, OffsetBookmark.class);
|
---|
[13798] | 258 | if (bookmarks != null) {
|
---|
[13797] | 259 | sanitizeBookmarks(bookmarks);
|
---|
[12084] | 260 | allBookmarks.addAll(bookmarks);
|
---|
[3779] | 261 | }
|
---|
[3715] | 262 | }
|
---|
| 263 |
|
---|
[13797] | 264 | static void sanitizeBookmarks(List<OffsetBookmark> bookmarks) {
|
---|
| 265 | // Retrieve layer id from layer name (it was not available before #13937)
|
---|
| 266 | bookmarks.stream().filter(b -> b.getImageryId() == null).forEach(b -> {
|
---|
| 267 | List<ImageryInfo> candidates = ImageryLayerInfo.instance.getLayers().stream()
|
---|
| 268 | .filter(l -> Objects.equals(l.getName(), b.getImageryName()))
|
---|
| 269 | .collect(Collectors.toList());
|
---|
| 270 | // Make sure there is no ambiguity
|
---|
| 271 | if (candidates.size() == 1) {
|
---|
| 272 | b.setImageryId(candidates.get(0).getId());
|
---|
| 273 | } else {
|
---|
| 274 | Logging.warn("Not a single layer for the name '" + b.getImageryName() + "': " + candidates);
|
---|
| 275 | }
|
---|
| 276 | });
|
---|
| 277 | // Update layer name (locale sensitive) if the locale has changed
|
---|
| 278 | bookmarks.stream().filter(b -> b.getImageryId() != null).forEach(b -> {
|
---|
| 279 | ImageryInfo info = ImageryLayerInfo.instance.getLayer(b.getImageryId());
|
---|
| 280 | if (info != null && !Objects.equals(info.getName(), b.getImageryName())) {
|
---|
| 281 | b.setImageryName(info.getName());
|
---|
| 282 | }
|
---|
| 283 | });
|
---|
[3715] | 284 | }
|
---|
| 285 |
|
---|
[13243] | 286 | /**
|
---|
| 287 | * Stores the bookmakrs in the settings.
|
---|
| 288 | */
|
---|
[3715] | 289 | public static void saveBookmarks() {
|
---|
[12851] | 290 | StructUtils.putListOfStructs(Config.getPref(), "imagery.offsetbookmarks", allBookmarks, OffsetBookmark.class);
|
---|
[3715] | 291 | }
|
---|
| 292 |
|
---|
[11651] | 293 | /**
|
---|
| 294 | * Returns all bookmarks.
|
---|
| 295 | * @return all bookmarks (unmodifiable collection)
|
---|
| 296 | * @since 11651
|
---|
| 297 | */
|
---|
| 298 | public static List<OffsetBookmark> getBookmarks() {
|
---|
| 299 | return Collections.unmodifiableList(allBookmarks);
|
---|
| 300 | }
|
---|
| 301 |
|
---|
| 302 | /**
|
---|
| 303 | * Returns the number of bookmarks.
|
---|
| 304 | * @return the number of bookmarks
|
---|
| 305 | * @since 11651
|
---|
| 306 | */
|
---|
| 307 | public static int getBookmarksSize() {
|
---|
| 308 | return allBookmarks.size();
|
---|
| 309 | }
|
---|
| 310 |
|
---|
| 311 | /**
|
---|
| 312 | * Adds a bookmark.
|
---|
| 313 | * @param ob bookmark to add
|
---|
| 314 | * @return {@code true}
|
---|
| 315 | * @since 11651
|
---|
| 316 | */
|
---|
| 317 | public static boolean addBookmark(OffsetBookmark ob) {
|
---|
| 318 | return allBookmarks.add(ob);
|
---|
| 319 | }
|
---|
| 320 |
|
---|
| 321 | /**
|
---|
| 322 | * Removes a bookmark.
|
---|
| 323 | * @param ob bookmark to remove
|
---|
| 324 | * @return {@code true} if this list contained the specified element
|
---|
| 325 | * @since 11651
|
---|
| 326 | */
|
---|
| 327 | public static boolean removeBookmark(OffsetBookmark ob) {
|
---|
| 328 | return allBookmarks.remove(ob);
|
---|
| 329 | }
|
---|
| 330 |
|
---|
| 331 | /**
|
---|
| 332 | * Returns the bookmark at the given index.
|
---|
| 333 | * @param index bookmark index
|
---|
| 334 | * @return the bookmark at the given index
|
---|
| 335 | * @throws IndexOutOfBoundsException if the index is out of range
|
---|
[13493] | 336 | * (<code>index < 0 || index >= size()</code>)
|
---|
[11651] | 337 | * @since 11651
|
---|
| 338 | */
|
---|
| 339 | public static OffsetBookmark getBookmarkByIndex(int index) {
|
---|
| 340 | return allBookmarks.get(index);
|
---|
| 341 | }
|
---|
| 342 |
|
---|
[13243] | 343 | /**
|
---|
| 344 | * Gets a bookmark that is usable on the given layer by it's name.
|
---|
| 345 | * @param layer The layer to use the bookmark at
|
---|
| 346 | * @param name The name of the bookmark
|
---|
| 347 | * @return The bookmark if found, <code>null</code> if not.
|
---|
| 348 | */
|
---|
[3734] | 349 | public static OffsetBookmark getBookmarkByName(ImageryLayer layer, String name) {
|
---|
| 350 | for (OffsetBookmark b : allBookmarks) {
|
---|
| 351 | if (b.isUsable(layer) && name.equals(b.name))
|
---|
| 352 | return b;
|
---|
| 353 | }
|
---|
| 354 | return null;
|
---|
| 355 | }
|
---|
| 356 |
|
---|
[13243] | 357 | /**
|
---|
| 358 | * Add a bookmark for the displacement of that layer
|
---|
| 359 | * @param name The bookmark name
|
---|
| 360 | * @param layer The layer to store the bookmark for
|
---|
| 361 | */
|
---|
| 362 | public static void bookmarkOffset(String name, AbstractTileSourceLayer<?> layer) {
|
---|
[3775] | 363 | LatLon center;
|
---|
[12630] | 364 | if (MainApplication.isDisplayingMapView()) {
|
---|
| 365 | center = Main.getProjection().eastNorth2latlon(MainApplication.getMap().mapView.getCenter());
|
---|
[3775] | 366 | } else {
|
---|
[9214] | 367 | center = LatLon.ZERO;
|
---|
[3775] | 368 | }
|
---|
[3734] | 369 | OffsetBookmark nb = new OffsetBookmark(
|
---|
[13797] | 370 | Main.getProjection().toCode(), layer.getInfo().getId(), layer.getInfo().getName(),
|
---|
[13243] | 371 | name, layer.getDisplaySettings().getDisplacement(), center);
|
---|
[8510] | 372 | for (ListIterator<OffsetBookmark> it = allBookmarks.listIterator(); it.hasNext();) {
|
---|
[3734] | 373 | OffsetBookmark b = it.next();
|
---|
| 374 | if (b.isUsable(layer) && name.equals(b.name)) {
|
---|
| 375 | it.set(nb);
|
---|
| 376 | saveBookmarks();
|
---|
| 377 | return;
|
---|
| 378 | }
|
---|
| 379 | }
|
---|
| 380 | allBookmarks.add(nb);
|
---|
| 381 | saveBookmarks();
|
---|
| 382 | }
|
---|
[12134] | 383 |
|
---|
| 384 | /**
|
---|
| 385 | * Converts the offset bookmark to a properties map.
|
---|
| 386 | *
|
---|
| 387 | * The map contains all the information to restore the offset bookmark.
|
---|
| 388 | * @return properties map of all data
|
---|
[12139] | 389 | * @see #fromPropertiesMap(java.util.Map)
|
---|
[12134] | 390 | * @since 12134
|
---|
| 391 | */
|
---|
| 392 | public Map<String, String> toPropertiesMap() {
|
---|
[12851] | 393 | return StructUtils.serializeStruct(this, OffsetBookmark.class);
|
---|
[12134] | 394 | }
|
---|
| 395 |
|
---|
| 396 | /**
|
---|
| 397 | * Creates an offset bookmark from a properties map.
|
---|
| 398 | * @param properties the properties map
|
---|
| 399 | * @return corresponding offset bookmark
|
---|
[12139] | 400 | * @see #toPropertiesMap()
|
---|
[12134] | 401 | * @since 12134
|
---|
| 402 | */
|
---|
| 403 | public static OffsetBookmark fromPropertiesMap(Map<String, String> properties) {
|
---|
[12851] | 404 | return StructUtils.deserializeStruct(properties, OffsetBookmark.class);
|
---|
[12134] | 405 | }
|
---|
[3715] | 406 | }
|
---|