Skip to content

Commit

Permalink
Initial support for Mapillary point clouds
Browse files Browse the repository at this point in the history
Signed-off-by: Taylor Smock <[email protected]>
  • Loading branch information
tsmock committed Mar 19, 2024
1 parent c551d3e commit d0f5504
Show file tree
Hide file tree
Showing 16 changed files with 781 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.openstreetmap.josm.plugins.mapillary.actions.MapPointObjectLayerAction;
import org.openstreetmap.josm.plugins.mapillary.actions.MapillaryDownloadAction;
import org.openstreetmap.josm.plugins.mapillary.actions.MapillaryExportAction;
import org.openstreetmap.josm.plugins.mapillary.actions.MapillaryPointCloudAction;
import org.openstreetmap.josm.plugins.mapillary.actions.MapillaryZoomAction;
import org.openstreetmap.josm.plugins.mapillary.data.mapillary.VectorDataSelectionListener;
import org.openstreetmap.josm.plugins.mapillary.data.mapillary.smartedit.IgnoredObjects;
Expand Down Expand Up @@ -104,6 +105,10 @@ public MapillaryPlugin(PluginInformation info) {
mapPointObjectLayerAction.updateEnabledState();
destroyables.add(mapPointObjectLayerAction);

MapillaryPointCloudAction mapillaryPointCloudAction = new MapillaryPointCloudAction();
MainMenu.add(menu.imagerySubMenu, mapillaryPointCloudAction, true); // FIXME expert until stable
destroyables.add(mapillaryPointCloudAction);

// TODO remove in destroy (not currently possible)
RequestProcessor.addRequestHandlerClass("photo", MapillaryRemoteControl.class);
RequestProcessor.addRequestHandlerClass("mapillaryfilter", MapillaryFilterRemoteControl.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapillary.actions;

import static org.openstreetmap.josm.tools.I18n.tr;

import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;

import javax.swing.JOptionPane;

import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.Notification;
import org.openstreetmap.josm.plugins.mapillary.MapillaryPlugin;
import org.openstreetmap.josm.plugins.mapillary.gui.layer.MapillaryLayer;
import org.openstreetmap.josm.plugins.mapillary.gui.layer.pointcloud.MapillaryPointCloudLayer;
import org.openstreetmap.josm.plugins.mapillary.oauth.OAuthUtils;
import org.openstreetmap.josm.plugins.mapillary.spi.preferences.MapillaryConfig;
import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryImageUtils;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.Shortcut;

/**
* An action to add point clouds as aerial imagery
*/
public class MapillaryPointCloudAction extends JosmAction {
/**
* Create a new action
*/
public MapillaryPointCloudAction() {
super(tr("Mapillary Point Cloud (experimental)"),
new ImageProvider(MapillaryPlugin.LOGO).setSize(ImageProvider.ImageSizes.DEFAULT),
tr("Open Mapillary Point Cloud layer"), Shortcut.registerShortcut("mapillary:pointcloud",
tr("Mapillary Point Cloud"), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE),
false, "mapillary:pointcloud", true);
}

@Override
public void actionPerformed(ActionEvent e) {
final var selected = MapillaryLayer.getInstance().getImage();
try {
var obj = OAuthUtils
.getWithHeader(URI.create(MapillaryConfig.getUrls().getImageInformation(selected.getUniqueId(),
MapillaryImageUtils.ImageProperties.SFM_CLUSTER)))
.getJsonObject(MapillaryImageUtils.ImageProperties.SFM_CLUSTER.toString());
if (obj.containsKey("id") && obj.containsKey("url")) {
MainApplication.getLayerManager()
.addLayer(new MapillaryPointCloudLayer(obj.getString("id"), obj.getString("url"), selected));
} else {
new Notification(tr("Could not find point cloud for Mapillary image {0}", selected.getUniqueId()))
.setIcon(JOptionPane.ERROR_MESSAGE).show();
}
} catch (IOException ioException) {
throw new UncheckedIOException(ioException);
}
}

@Override
protected void updateEnabledState() {
setEnabled(MapillaryLayer.hasInstance() && MapillaryLayer.getInstance().getImage() != null || true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapillary.data.mapillary.pointcloud;

public record PointCloudCamera(ProjectionType projectionType, int width, int height, double focal, double k1,
double k2) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapillary.data.mapillary.pointcloud;

public record PointCloudColor(int r, int g, int b) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapillary.data.mapillary.pointcloud;

import org.openstreetmap.josm.data.coor.ILatLon;

public record PointCloudLatLonAlt(double lat, double lon, double altitude) implements ILatLon {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapillary.data.mapillary.pointcloud;

public record PointCloudPoint(PointCloudXYZ coordinates, PointCloudColor color) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapillary.data.mapillary.pointcloud;

import java.util.Map;

public record PointCloudReconstruction(PointCloudLatLonAlt referenceLatLonAlt, Map<String, PointCloudCamera> cameras,
Map<String, PointCloudShot> shots, Map<String, PointCloudPoint> points) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapillary.data.mapillary.pointcloud;

import java.time.Instant;

public record PointCloudShot(String cameraId, PointCloudXYZ rotation, PointCloudXYZ translation,
PointCloudXYZ gpsPosition, double gpsDop, int orientation, Instant captureTime) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapillary.data.mapillary.pointcloud;

public record PointCloudXYZ(double x, double y, double z) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapillary.data.mapillary.pointcloud;

public enum ProjectionType {
PERSPECTIVE, BROWN, FISHEYE, EQUIRECTANGULAR
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapillary.gui.layer.pointcloud;

import static org.openstreetmap.gui.jmapviewer.OsmMercator.MERCATOR_256;

import java.awt.Point;

import org.openstreetmap.gui.jmapviewer.Coordinate;
import org.openstreetmap.gui.jmapviewer.OsmMercator;
import org.openstreetmap.gui.jmapviewer.Projected;
import org.openstreetmap.gui.jmapviewer.Tile;
import org.openstreetmap.gui.jmapviewer.TileRange;
import org.openstreetmap.gui.jmapviewer.TileXY;
import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
import org.openstreetmap.gui.jmapviewer.interfaces.IProjected;
import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTMSTileSource;
import org.openstreetmap.josm.data.coor.ILatLon;
import org.openstreetmap.josm.data.imagery.ImageryInfo;

/**
* The source for creating tiles for Mapillary Point Clouds
*/
public class MapillaryPointCloudImageSource extends AbstractTMSTileSource {

private final ILatLon origin;

public MapillaryPointCloudImageSource(ImageryInfo info, ILatLon origin) {
super(info);
this.origin = origin;
}

@Override
public double getDistance(double lat1, double lon1, double lat2, double lon2) {
return osmMercator().getDistance(lat1, lon1, lat2, lon2);
}

@Override
public Point latLonToXY(double lat, double lon, int zoom) {
return new Point((int) Math.round(osmMercator().lonToX(lon, zoom)),
(int) Math.round(osmMercator().latToY(lat, zoom)));
}

@Override
public ICoordinate xyToLatLon(int x, int y, int zoom) {
return new Coordinate(osmMercator().yToLat(y, zoom), osmMercator().xToLon(x, zoom));
}

@Override
public TileXY latLonToTileXY(double lat, double lon, int zoom) {
return new TileXY(osmMercator().lonToX(lon, zoom) / getTileSize(),
osmMercator().latToY(lat, zoom) / getTileSize());
}

@Override
public ICoordinate tileXYToLatLon(int x, int y, int zoom) {
return new Coordinate(osmMercator().yToLat((long) y * getTileSize(), zoom),
osmMercator().xToLon((long) x * getTileSize(), zoom));
}

@Override
public IProjected tileXYtoProjected(int x, int y, int zoom) {
final var mercatorWidth = 2 * Math.PI * OsmMercator.EARTH_RADIUS;
final var f = mercatorWidth * getTileSize() / osmMercator().getMaxPixels(zoom);
return new Projected(f * x - mercatorWidth / 2, -(f * y - mercatorWidth / 2));
}

@Override
public TileXY projectedToTileXY(IProjected p, int zoom) {
final var mercatorWidth = 2 * Math.PI * OsmMercator.EARTH_RADIUS;
final var f = mercatorWidth * getTileSize() / osmMercator().getMaxPixels(zoom);
return new TileXY((p.getEast() + mercatorWidth / 2) / f, (-p.getNorth() + mercatorWidth / 2) / f);
}

@Override
public boolean isInside(Tile inner, Tile outer) {
final int dz = inner.getZoom() - outer.getZoom();
if (dz < 0)
return false;
return outer.getXtile() == inner.getXtile() >> dz && outer.getYtile() == inner.getYtile() >> dz;
}

@Override
public TileRange getCoveringTileRange(Tile tile, int newZoom) {
if (newZoom <= tile.getZoom()) {
final int dz = tile.getZoom() - newZoom;
final var xy = new TileXY(tile.getXtile() >> dz, tile.getYtile() >> dz);
return new TileRange(xy, xy, newZoom);
} else {
final int dz = newZoom - tile.getZoom();
final var t1 = new TileXY(tile.getXtile() << dz, tile.getYtile() << dz);
final var t2 = new TileXY(t1.getX() + (1 << dz) - 1, t1.getY() + (1 << dz) - 1);
return new TileRange(t1, t2, newZoom);
}
}

@Override
public String getServerCRS() {
return "EPSG:3857";
}

private static OsmMercator osmMercator() {
return MERCATOR_256;
}
}
Loading

0 comments on commit d0f5504

Please sign in to comment.