Closed reeddolphin closed 1 year ago
The Azure Maps drawing tools don't have a copy/paste feature, so adding this to the labelling tool would likely be a lot of work. That said, I have been playing around with an idea of how this would work and some of us are considering creating and open source "advanced" drawing tools module for Azure Maps which might be the way to go here.
I took a stab at creating a rough example of a copy/paste extension for the Azure Maps drawing tools. I haven't tested this a lot, so might be buggy, but seems to work well. Good starting point.
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
<link href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" rel="stylesheet" />
<script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js"></script>
<!-- Add references to the Azure Maps Map Drawing Tools JavaScript and CSS files. -->
<link rel="stylesheet" href="https://atlas.microsoft.com/sdk/javascript/drawing/1/atlas-drawing.min.css" type="text/css" />
<script src="https://atlas.microsoft.com/sdk/javascript/drawing/1/atlas-drawing.min.js"></script>
<script>
var map, drawingManager;
var copiedShape;
var mousePosition;
var dmSource;
function GetMap() {
//Initialize a map instance.
map = new atlas.Map('myMap', {
view: 'Auto',
authOptions: {
authType: 'subscriptionKey',
subscriptionKey: '<Your Azure Maps Key>'
}
});
//Wait until the map resources are ready.
map.events.add('ready', function () {
//Create an instance of the drawing manager and display the drawing toolbar.
drawingManager = new atlas.drawing.DrawingManager(map, {
toolbar: new atlas.control.DrawingToolbar({ position: 'top-right', style: 'light' })
});
dmSource = drawingManager.getSource();
//Monitor the mouse position over the map.
map.events.add('mousemove', mouseMoved);
map.events.add('mouseout', () => { mousePosition = null });
//Add keyboard shortcuts for copy/paste of shapes.
map.getMapContainer().addEventListener('keyup', keyup);
});
}
function keyup(e) {
//Check to see if the control button is held.
if(e.ctrlKey) {
//Check to see if user pressed C to copy.
if(e.keyCode === 67) {
//Copy the last drawn shape in the drawing manager.
var s = dmSource.getShapes();
if(s.length > 0) {
//Copy JSON version of shape.
copiedShape = s[s.length - 1].toJson();
//Delete ids to prevent issues.
delete copiedShape.id;
delete copiedShape.properties._azureMapsShapeId;
} else {
copiedShape = null;
}
}
//Check to see if user pressed V to paste.
else if (e.keyCode === 86){
pasteShape();
}
}
}
function mouseMoved(e) {
mousePosition= e.position;
}
/*function shapeClicked(e) {
//Check to see if in copy/paste mode.
var elm = document.getElementById('copyPasteMode');
if(elm.checked){
var ds = drawingManager.getSource();
//Check to see if the clicked shape is in the drawing manager data source.
for(var i=0;i< e.shapes.length;i++){
//Ensure object is a Shape and is in the drawing manager data source.
if(e.shapes[i] instanceof atlas.Shape && ds.getShapeById(e.shapes[i].getId())) {
//Storing json copy.
copiedShape = e.shapes[i].toJson();
//Delete ids to prevent issues.
delete copiedShape.id;
delete copiedShape.properties._azureMapsShapeId;
//Only select the first selected shape.
return;
}
}
}
}*/
function pasteShape() {
//Paste the shape to where the mouse is over the map, or the center of the map.
var pasteCenter = mousePosition || map.getCamera().center;
if(copiedShape) {
//Calculate the center point of the copied shape based on bounding box for simplicity.
var copiedCenter = atlas.data.BoundingBox.getCenter(atlas.data.BoundingBox.fromData(copiedShape));
//Calculate the offsets. Use pixels at zoom level 22 for visible accuracy.
var p = atlas.math.mercatorPositionsToPixels([copiedCenter, pasteCenter], 22);
var dx = p[1][0] - p[0][0];
var dy = p[1][1] - p[0][1];
var shapeToPaste = createShapeToPaste(dx, dy);
var ds = drawingManager.getSource();
ds.add(shapeToPaste);
//Get the last shape added to the data source and put it into edit mode.
var shapes = ds.getShapes();
var s = shapes[shapes.length - 1];
drawingManager.edit(s);
}
}
function createShapeToPaste(dx, dy) {
var g = copiedShape.geometry;
var newGeometry = {
type: g.type,
coordinates: []
};
//Offset the positions of the geometry.
switch(g.type) {
case 'Point':
newGeometry.coordinates = getOffsetPositions([g.coordinates], dx, dy)[0];
break;
case 'LineString':
case 'MultiPoint':
newGeometry.coordinates = getOffsetPositions(g.coordinates, dx, dy);
break;
case 'Polygon':
case 'MultiLineString':
newGeometry.coordinates = g.coordinates.map(r => {
return getOffsetPositions(r, dx, dy);
});
break;
//MultiPolygon
}
//Create a GeoJSON featuret from new geometry, copy the properties.
return new atlas.data.Feature(newGeometry, JSON.parse(JSON.stringify(copiedShape.properties)));
}
function getOffsetPositions(positions, dx, dy) {
//Convert positions to pixel at zoom level 22.
var pixels = atlas.math.mercatorPositionsToPixels(positions, 22);
//Offset pixels.
for(var i=0, len = pixels.length; i< len;i++) {
pixels[i][0] += dx;
pixels[i][1] += dy;
}
//Convert back to positions.
return atlas.math.mercatorPixelsToPositions(pixels, 22);
}
</script>
</head>
<body onload="GetMap()">
<div id="myMap" style="position:relative;width:100%;min-width:290px;height:600px;"></div>
<input id="copyPasteMode" type="checkbox" onclick="toggleCopyPasteMode()" />
<fieldset style="width:calc(100% - 30px);min-width:290px;margin-top:10px;">
<legend>Drawing tools - copy/paste extension</legend>
This is a rough example of adding a copy/paste capability to the drawing tools in Azure Maps.
When the map has focus and you press CTRL + C, the last added shape in the drawing manager will be copied to memory.
When you press CTRL + V, the copied shape will be added to the map, centered over the mouse pointer if it is above the map, otherwise the shape will be added to the center of the map.
Once a shape has been added, the drawing tools go into edit mode for that shape so that fine tune adjustments can be made by dragging the shape.
Note that circles will maintain their radiu, and as such they could have a different asize pixel area.
</fieldset>
</body>
</html>
Ah that's awesome, appreciate the super quick response! I had a hard time getting the drawing keyboard shortcuts to work in the non-local Labeler so I was hopeful my fruitless search for a c&p feature in Azure Maps wasn't going to apply here.
I am a bit of a html neophyte so I might have to come back to your extension at a later point 😅 Is there a project roadmap for the satellite-imagery-labeling-tool or is it mostly in the POC phase these days? the simplicity of it's design is very appealing!
The satellite-imagery-labeling-tool is a bit of a side project. It was created as part of a disaster response mission some of us were working on last year and we made it open source and have been improving it from time to time. Inside of Microsoft it's been used in at least a dozen different projects so far and has a lot more usage than we expected.
I took a crack at this today and added in copy paste support. To use it, do the following:
CTRL + c
to copy the selected shape into memory.CTRL + v
any time afterwards to paste the shape to the map. In the settings you can control how the shapes offset when pasted to the map.
wow awesome thanks so much! it's a really neat tool and it's awesome that it's OSS.
I have a project that requires labeling lots of features of identical size and shapes within close proximity.
In the Labeler, I would like to be able to
Select
a Polygon/Rectangle/Circle, and select a UI "copy" element (or even better, aCTRL+C
shortcut) and then "paste" (ideally, aCTRL+V
shortcut) onto a different section of the map.