How to rotate/transform mapbox-gl-draw features? - mapbox-gl-js

I'm using mapbox-gl-draw to add move-able features to my map. In addition to movability functionality, I am needing rotate/transform -ability functionality for the features akin to Leaflet.Path.Transform.
At current, would my only option to achieve be to create a custom mode?
e.g. something like:
map.on('load', function() {
Draw.changeMode('transform');
});
I am not able to convert my map and it's features to mapbox-gl-leaflet in order to implement Leaflet.Path.Transform as losing rotation / bearing / pitch support is not an option.

Long answer incoming. (see http://mapster.me/mapbox-gl-draw-rotate-mode and http://npmjs.com/package/mapbox-gl-draw-rotate-mode for some final products, https://github.com/mapstertech/mapbox-gl-draw-rotate-mode)
I've been working on something similar for a custom project, and not using a draw library. My project involves some pretty regularly sized objects, not very complex polygons, so the solution might be too simple for you but it may be the right path. I just have rotate and move.
Doing movement isn't too hard geographically. Here's some help to get you started. A basic JSBin is up at https://jsbin.com/yoropolewo/edit?html,output with some drag functionality (too tired to do rotate too).
First, register the necessary click events to have a dragging event. You can listen on the specific Mapbox layers for a mousedown, then on the whole document for a mousemove and mouseup.
To do individual shape rotation, you need to ensure that you are referring to the right feature. In this example I assume there's just one feature in the source data, but that's probably too simple for most uses, so you have to extrapolate. The source data is what we affect when we setData() later on. There are obviously numerous ways to do what I'm doing here, but I'm trying to be clear.
var currentDragging = false;
var currentDraggingFeature = false;
var currentDraggingType = false;
var firstDragEvent = false;
map.on('mousedown','my-layer-id',function(e) {
currentDragging = 'my-source-id'; // this must correspond to the source-id of the layer
currentDraggingFeature = e.features[0]; // you may have to filter this to make sure it's the right feature
currentDraggingType = 'move'; // rotation or move
firstDragEvent = map.unproject([e.originalEvent.layerX,e.originalEvent.layerY]);
});
window.addEventListener('mousemove',dragEvent);
window.addEventListener('mouseup',mouseUpEvent);
You will need a function, then, that takes an initial point, a distance, and a rotation, and returns a point back to you. Like this:
Number.prototype.toRad = function() {
return this * Math.PI / 180;
}
Number.prototype.toDeg = function() {
return this * 180 / Math.PI;
}
function getPoint(point, brng, dist) {
dist = dist / 63.78137; // this number depends on how you calculate the distance
brng = brng.toRad();
var lat1 = point.lat.toRad(), lon1 = point.lng.toRad();
var lat2 = Math.asin(Math.sin(lat1) * Math.cos(dist) +
Math.cos(lat1) * Math.sin(dist) * Math.cos(brng));
var lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(dist) *
Math.cos(lat1),
Math.cos(dist) - Math.sin(lat1) *
Math.sin(lat2));
if (isNaN(lat2) || isNaN(lon2)) return null;
return [lon2.toDeg(),lat2.toDeg()];
}
Now, the key is the unproject method in Mapbox GL JS, so you can move between x/y coordinates on the mouse and lng/lat on your map. Then, using the map.getSource().setData() function to set a new geoJSON.
I am turning the x/y into coordinates immediately here but you can do it at any point. Something like the following for moving:
function moveEvent(e) {
// In the case of move, you are just translating the points based on distance and angle of the drag
// Exactly how your translate your points here can depend on the shape
var geoPoint = map.unproject([e.layerX,e.layerY]);
var xDrag = firstDragEvent.lng - geoPoint.lng;
var yDrag = firstDragEvent.lat - geoPoint.lat;
var distanceDrag = Math.sqrt( xDrag*xDrag + yDrag*yDrag );
var angle = Math.atan2(xDrag, yDrag) * 180 / Math.PI;
// Once you have this information, you loop over the coordinate points you have and use a function to find a new point for each
var newFeature = JSON.parse(JSON.stringify(currentDraggingFeature));
if(newFeature.geometry.type==='Polygon') {
var newCoordinates = [];
newFeature.geometry.coordinates.forEach(function(coords) {
newCoordinates.push(getPoint(coords,distanceDrag,angle));
});
newFeature.geometry.coordinates = newCoordinates;
}
map.getSource(currentDragging).setData(newFeature);
}
Rotating is a little harder because you want the shape to rotate around a central point, and you need to know the distance of each point to that central point in order to do that. If you have a simple square polygon this calculation would be easy. If not, then using something like this would be helpful (Finding the center of Leaflet polygon?):
var getCentroid2 = function (arr) {
var twoTimesSignedArea = 0;
var cxTimes6SignedArea = 0;
var cyTimes6SignedArea = 0;
var length = arr.length
var x = function (i) { return arr[i % length][0] };
var y = function (i) { return arr[i % length][1] };
for ( var i = 0; i < arr.length; i++) {
var twoSA = x(i)*y(i+1) - x(i+1)*y(i);
twoTimesSignedArea += twoSA;
cxTimes6SignedArea += (x(i) + x(i+1)) * twoSA;
cyTimes6SignedArea += (y(i) + y(i+1)) * twoSA;
}
var sixSignedArea = 3 * twoTimesSignedArea;
return [ cxTimes6SignedArea / sixSignedArea, cyTimes6SignedArea / sixSignedArea];
}
Once you have the ability to know the polygon's center, you're golden:
function rotateEvent(e) {
// In the case of rotate, we are keeping the same distance from the center but changing the angle
var findPolygonCenter = findCenter(currentDraggingFeature);
var geoPoint = map.unproject([e.layerX,e.layerY]);
var xDistanceFromCenter = findPolygonCenter.lng - geoPoint.lng;
var yDistanceFromCenter = findPolygonCenter.lat - geoPoint.lat;
var angle = Math.atan2(xDistanceFromCenter, yDistanceFromCenter) * 180 / Math.PI;
var newFeature = JSON.parse(JSON.stringify(currentDraggingFeature));
if(newFeature.geometry.type==='Polygon') {
var newCoordinates = [];
newFeature.geometry.coordinates.forEach(function(coords) {
var xDist = findPolygonCenter.lng - coords[0];
var yDist = findPolygonCenter.lat - coords[1];
var distanceFromCenter = Math.sqrt( xDist*xDist + yDist*yDist );
var rotationFromCenter = Math.atan2(xDist, yDist) * 180 / Math.PI;
newCoordinates.push(
getPoint(coords,distanceFromCenter,rotationFromCenter+angle)
);
});
newFeature.geometry.coordinates = newCoordinates;
}
}
Of course, throughout, ensure that your coordinates are being passed and returned correctly from functions. Some of this code may have incorrect levels of arrays in it. It's very easy to run into bugs with the lat/lng object versus the geoJSON arrays.
I hope the explanation is brief but clear enough, and that you understand logically what we are doing to reorient these points. That's the main point, the exact code is details.
Maybe I should just make a module or fork GL Draw...

Related

ChartJS - line graph, position tooltip

I have the following graph with 3 datasets. Everything works fine except for one small bug. I want to tooltip to be placed only on the first dataset, as it currently is placed on the average position of all datasets.
Here is a screenshot:
I know that ChartJS has the positioners function (the one below), but I can't figure out how to ignore the other 2 datasets so the tooltip is sticked only to the first line graph
Chart.Tooltip.positioners.top = function (elements, eventPosition) {
const tooltip = this;
return ;
};
You can also define a custom position function for tooltips. like this,
Chart.Tooltip.positioners.custom = function(elements, eventPosition) {
var x = eventPosition.x;
var y = eventPosition.y;
var minDistance = Number.POSITIVE_INFINITY;
var i, len, nearestElement;
var top_element = elements[0];
for (i = 0, len = elements.length; i < len; ++i) {
console.log(elements[i].tooltipPosition().y);
if (elements[i].tooltipPosition().y < top_element.tooltipPosition().y) {
top_element = elements[i];
}
}
var tp = top_element.tooltipPosition();
x = tp.x;
y = tp.y;
return {
x: x,
y: y
};
};
Once done, you can specify it in tooltips.options.
More information :
https://www.chartjs.org/docs/latest/configuration/tooltip.html#position-modes
Hope it helps!

How target a movieClip in animate cc in this drag drop code

is there a way to modify this code for animate cc to make object in the stage and interact with it ?
it is a bit of pain to make drag and drop in createjs for animate cc
there is nothing in the web that describe how to do it for animate cc or flash cc even the documentation has nothing to tell about drag and drop in the canvas
//Stage
var stage = new createjs.Stage("demoCanvas");
//VARIABLES
//Drag Object Size
dragRadius = 40;
//Destination Size
destHeight = 100;
destWidth = 100;
//Circle Creation
var label = new createjs.Text("DRAG ME", "14px Lato", "#fff");
label.textAlign="center";
label.y -= 7;
var circle = new createjs.Shape();
circle.graphics.setStrokeStyle(2).beginStroke("black")
.beginFill("red").drawCircle(0,0, dragRadius);
//Drag Object Creation
//Placed inside a container to hold both label and shape
var dragger = new createjs.Container();
dragger.x = dragger.y = 100;
dragger.addChild(circle, label);
dragger.setBounds(100, 100, dragRadius*2, dragRadius*2);
//DragRadius * 2 because 2*r = width of the bounding box
var label2 = new createjs.Text("HERE", "bold 14px Lato", "#000");
label2.textAlign = "center";
label2.x += 50;
label2.y += 40;
var box = new createjs.Shape();
box.graphics.setStrokeStyle(2).beginStroke("black").rect(0, 0, destHeight, destWidth);
var destination = new createjs.Container();
destination.x = 350;
destination.y = 50;
destination.setBounds(350, 50, destHeight, destWidth);
destination.addChild(label2, box);
//DRAG FUNCTIONALITY =====================
dragger.on("pressmove", function(evt){
evt.currentTarget.x = evt.stageX;
evt.currentTarget.y = evt.stageY;
stage.update(); //much smoother because it refreshes the screen every pixel movement instead of the FPS set on the Ticker
if(intersect(evt.currentTarget, destination)){
evt.currentTarget.alpha=0.2;
box.graphics.clear();
box.graphics.setStrokeStyle(3)
.beginStroke("#0066A4")
.rect(0, 0, destHeight, destWidth);
}else{
evt.currentTarget.alpha=1;
box.graphics.clear(); box.graphics.setStrokeStyle(2).beginStroke("black").rect(0, 0, destHeight, destWidth);
}
});
//Mouse UP and SNAP====================
dragger.on("pressup", function(evt) {
if(intersect(evt.currentTarget, destination)){
dragger.x = destination.x + destWidth/2;
dragger.y = destination.y + destHeight/2;
dragger.alpha = 1;
box.graphics.clear();
box.graphics.setStrokeStyle(2).beginStroke("black").rect(0, 0, destHeight, destWidth);
stage.update(evt);
}
});
//Tests if two objects are intersecting
//Sees if obj1 passes through the first and last line of its
//bounding box in the x and y sectors
//Utilizes globalToLocal to get the x and y of obj1 in relation
//to obj2
//PRE: Must have bounds set for each object
//Post: Returns true or false
function intersect(obj1, obj2){
var objBounds1 = obj1.getBounds().clone();
var objBounds2 = obj2.getBounds().clone();
var pt = obj1.globalToLocal(objBounds2.x, objBounds2.y);
var h1 = -(objBounds1.height / 2 + objBounds2.height);
var h2 = objBounds2.width / 2;
var w1 = -(objBounds1.width / 2 + objBounds2.width);
var w2 = objBounds2.width / 2;
if(pt.x > w2 || pt.x < w1) return false;
if(pt.y > h2 || pt.y < h1) return false;
return true;
}
//Adds the object into stage
stage.addChild(destination, dragger);
stage.mouseMoveOutside = true;
stage.update();
thanks
I am not exactly sure what you are asking. The demo you showed works fine (looks like it came from this codepen), and it is not clear what you are trying to add. This demo was made directly in code, not with Animate CC - which is really good for building assets, animations, and display list structure, but you should write application code around what gets exported.
There are plenty of documentation and examples online for Drag and Drop, in the EaselJS GitHub, and EaselJS docs:
DragAndDrop demo in GitHub
Live demo on EaselJS demos page
Documentation on pressMove
Tutorial on Mouse Events which includes Drag and Drop
I recommend narrowing down what you are trying to do, show what code or approaches you have tried so far, and posting specific questions here.
Lastly, here is the first part of an ongoing series for working with Animate CC: http://blog.gskinner.com/archives/2015/04/introduction-to-the-flash-cc-html5-canvas-document.html
Cheers.

Using Bing Maps Quadkeys as Openlayers 3 Tile source

I have a number of tile sources which utilise Bing Maps' Quadkey system in an old Silverlight application and I would like to use them in a new Openlayers 3 map.
I have found several examples of functions which will convert these sources for Leaflet.js but the syntax is somewhat different for OL3 and reading through the API docs indicates that there is an ol.Tile.coord class but if I understand correctly this is an experimental feature and might require a custom build from the source code.
There is reference to such functionality on the GitHub pages but I don't know if I have to compile a build with this source:
https://github.com/openlayers/ol3/blob/5c5364bbb7e8df76f18242ad665c87ca08a76e76/src/ol/source/bingmapssource.js
Can anyone provide an example of this type of conversion or indeed does anyone know if the latest (3.8.2) version of OL3 supports the quadkey method?
This is the leaflet example:
var BingLayer = L.TileLayer.extend({
getTileUrl: function (tilePoint) {
this._adjustTilePoint(tilePoint);
return L.Util.template(this._url, {
s: this._getSubdomain(tilePoint),
q: this._quadKey(tilePoint.x, tilePoint.y, this._getZoomForUrl())
});
},
_quadKey: function (x, y, z) {
var quadKey = [];
for (var i = z; i > 0; i--) {
var digit = '0';
var mask = 1 << (i - 1);
if ((x & mask) != 0) {
digit++;
}
if ((y & mask) != 0) {
digit++;
digit++;
}
quadKey.push(digit);
}
return quadKey.join('');
}
});
And this is the exisiting Silverlight code:
public override Uri GetUri(int x, int y, int zoomLevel, bool getPrintLink)
{
Uri uri = null;
if (this.Covers(x, y, zoomLevel))
{
QuadKey qk = new QuadKey(x, y, zoomLevel);
if (getPrintLink)
{
uri = new Uri(this.CurrentHostURL + "/tiles/NL/" + zoomLevel.ToString() + "/" + qk.Key + ".ipic", UriKind.RelativeOrAbsolute);
}
else
{
uri = new Uri("http://tileserver.satmap.com/NL/" + zoomLevel.ToString() + "/" + qk.Key + ".ipic", UriKind.RelativeOrAbsolute);
}
}
return uri;
}
Any insight would be appreciated as I've trawled many forums and countless pages of search results without finding a solution.
As far as I can see, your _quadKey function is correct. The issue that might arise is understanding the ol.TileCoord provided to the URL function.
In OpenLayers 3.7 and later, all TileCoords are calculated with the top left corner as their origin. Furthermore, both the X and Y coordinates increase naturally, so that the X and Y coordinates of a TileCoord corresponds to the normal concepts of a two-dimensional axis.
The top left tile of a given zoom level will always have X=0 and Y=-1. The tile below that will have X=0 and Y=-2; Y will always be negative.
Some mapping applications, such as Bing, also use the top left corner as the tile origin, but lets the Y coordinate increase downwards. The top left tile would the be X=0 and Y=0, while the tile below would be X=0 and Y=1.
In order to calculate the quadkeys, the Y coordinate has to be inverted and adjusted by one. This should work:
// this is unchanged from the question
var quadkey = function (x, y, z) {
var quadKey = [];
for (var i = z; i > 0; i--) {
var digit = '0';
var mask = 1 << (i - 1);
if ((x & mask) != 0) {
digit++;
}
if ((y & mask) != 0) {
digit++;
digit++;
}
quadKey.push(digit);
}
return quadKey.join('');
};
var quadKeyLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
maxZoom: 19,
tileUrlFunction: function (tileCoord, pixelRatio, projection) {
var z = tileCoord[0];
var x = tileCoord[1];
var y = -tileCoord[2] - 1;
return "//example.com/r" + quadkey(x, y, z);
}
})
});

Using leapmotion to control Unity3d interface

I understand that I can use leapmotion in game with Unity3D.
What I can't see any information on, is if I can use it to actual interact with assets, models etc as I build the game. For example revolving a game object around the x axis or zooming in or out of the view.
Is this possible?
Yes, it is possible, but requires some scripts that nobody has written yet (ASFAIK). Here is a VERY rough example that I worked up today since I've been curious about this question, too.
All it does is move, scale, and rotate a selected game object -- it doesn't try to do this in a good way -- it is a proof of concept only. To make it work you would have to do a sensible conversion of Leap coordinates and rotations to Unity values. To try it, put this script in a folder called "Editor", select a game object in the scene view and hold a key down while moving your hand above your Leap. As I said, none of these movements really work to edit an object, but you can see that it is possible with some sensible logic.
#CustomEditor (Transform)
class RotationHandleJS extends Editor {
var controller = new Leap.Controller();
var position;
var localScale;
var localRotation;
var active = false;
function OnSceneGUI () {
e = Event.current;
switch (e.type) {
case EventType.KeyDown:
position = target.transform.position;
localScale = target.transform.localScale;
localRotation = target.transform.localRotation;
active = true;
Debug.Log("editing");
break;
case EventType.KeyUp:
active = false;
target.transform.position = position;
target.transform.localScale = localScale;
EditorUtility.SetDirty (target);
break;
}
if(active){
frame = controller.Frame();
ten = controller.Frame(10);
scale = frame.ScaleFactor(ten);
translate = frame.Translation(ten);
target.transform.localScale = localScale + new Vector3(scale, scale, scale);
target.transform.position = position + new Vector3(translate.x, translate.y, translate.z);
leapRot = frame.RotationMatrix(ten);
quats = convertRotation(leapRot);
target.transform.localRotation = quats;
}
}
var LEAP_UP = new Leap.Vector(0, 1, 0);
var LEAP_FORWARD = new Leap.Vector(0, 0, -1);
var LEAP_ORIGIN = new Leap.Vector(0, 0, 0);
function convertRotation(matrix:Leap.Matrix) {
var up = matrix.TransformDirection(LEAP_UP);
var forward = matrix.TransformDirection(LEAP_FORWARD);
return Quaternion.LookRotation(new Vector3(forward.x, forward.y,forward.z), new Vector3(up.x, up.y, up.z));
}
}

Google Earth API Moving a polygon

I just starting coding with Google Earth using the GEPlugin control for .Net and still got a lot to learn.
What has got me puzzled is when I try to drag a polygon.
The method below is called whenever the mousemove event fires and should be moving each point of the polygon while retaining the orginal shape of the polygon. The lat / long for each point is changed but the polygon does not move position on the map.
Will moving a point in a polygon cause it to redraw, do I need to call a method to force a redraw or perhaps do something else entirely?
Thanks!
private void DoMouseMove(IKmlMouseEvent mouseEvent)
{
if (isDragging)
{
mouseEvent.preventDefault();
var placemark = mouseEvent.getTarget() as IKmlPlacemark;
if (placemark == null)
{
return;
}
IKmlPolygon polygon = placemark.getGeometry() as IKmlPolygon;
if (polygon != null)
{
float latOffset = startLatLong.Latitude - mouseEvent.getLatitude();
float longOffset = startLatLong.Longitude - mouseEvent.getLongitude();
KmlLinearRingCoClass outer = polygon.getOuterBoundary();
KmlCoordArrayCoClass coordsArray = outer.getCoordinates();
for(int i = 0; i < coordsArray.getLength(); i++)
{
KmlCoordCoClass currentPoint = coordsArray.get(i);
currentPoint.setLatLngAlt(currentPoint.getLatitude() + latOffset,
currentPoint.getLongitude() + longOffset, 0);
}
}
}
}
Consider voting for these issues to be resolved
http://code.google.com/p/earth-api-utility-library/issues/detail?id=33
http://code.google.com/p/earth-api-samples/issues/detail?id=167
You may find some hints at the following link:
http://earth-api-utility-library.googlecode.com/svn/trunk/extensions/examples/ruler.html
UPDATE:
I've released the extension library: https://bitbucket.org/mutopia/earth
See https://bitbucket.org/mutopia/earth/src/master/sample/index.html to run it.
See the drag() method in the sample code class, which calls setDragMode() and addDragEvent() to enable dragging of the KmlPolygon.
I successfully implemented this using takeOverCamera in the earth-api-utility-library and three events:
setDragMode: function (mode) {
// summary:
// Sets dragging mode on and off
if (mode == this.dragMode) {
Log.info('Drag mode is already', mode);
} else {
this.dragMode = mode;
Log.info('Drag mode set', mode);
if (mode) {
this.addEvent(this.ge.getGlobe(), 'mousemove', this.dragMouseMoveCallback);
this.addEvent(this.ge.getGlobe(), 'mouseup', this.dragMouseUpCallback);
this.addEvent(this.ge.getView(), 'viewchange', this.dragViewChange, false);
} else {
this.removeEvent(this.ge.getGlobe(), 'mousemove', this.dragMouseMoveCallback);
this.removeEvent(this.ge.getGlobe(), 'mouseup', this.dragMouseUpCallback);
this.removeEvent(this.ge.getView(), 'viewchange', this.dragViewChange, false);
}
}
},
This is in a utility library within a much larger project. dragMode is a boolean which adds and removes events. These three events control what happens when you drag. addEvent and removeEvent are my own wrapper functions:
addEvent: function (targetObject, eventID, listenerCallback, capture) {
// summary:
// Convenience method for google.earth.addEventListener
capture = setDefault(capture, true);
google.earth.addEventListener(targetObject, eventID, listenerCallback, capture);
},
removeEvent: function (targetObject, eventID, listenerCallback, capture) {
// summary:
// Convenience method for google.earth.removeEventListener
capture = setDefault(capture, true);
google.earth.removeEventListener(targetObject, eventID, listenerCallback, capture);
},
Ignoring the minor details, all the important stuff is in the callbacks to those events. The mousedown event locks the camera and sets the polygon I'm dragging as the dragObject (it's just a variable I'm using). It saves the original lat long coordinates.
this.dragMouseDownCallback = lang.hitch(this, function (event) {
var obj = event.getTarget();
this.lockCamera(true);
this.setSelected(obj);
this.dragObject = obj;
this.dragLatOrigin = this.dragLatLast = event.getLatitude();
this.dragLngOrigin = this.dragLngLast = event.getLongitude();
}
The mousemove callback updates to the latest lat long coordinates:
this.dragMouseMoveCallback = lang.hitch(this, function (event) {
if (this.dragObject) {
var lat = event.getLatitude();
var lng = event.getLongitude();
var latDiff = lat - this.dragLatLast;
var lngDiff = lng - this.dragLngLast;
if (Math.abs(latDiff) > this.dragSensitivity || Math.abs(lngDiff > this.dragSensitivity)) {
this.addPolyCoords(this.dragObject, [latDiff, lngDiff]);
this.dragLatLast = lat;
this.dragLngLast = lng;
}
}
});
Here I'm using some fancy sensitivity values to prevent updating this too often. Finally, addPolyCoords is also my own function which adds lat long values to the existing coordinates of the polygon - effectively moving it across the globe. I do this with the built in setLatitude() and setLongitude() functions for each coordinate. You can get the coordinates like so, where polygon is a KmlPolyon object:
polygon.getGeometry().getOuterBoundary().getCoordinates()
And of course, the mousedown callback turns off the drag mode so that moving the mouse doesn't continue to drag the polygon:
this.dragMouseUpCallback = lang.hitch(this, function (event) {
if (this.dragObject) {
Log.info('Stop drag', this.dragObject.getType());
setTimeout(lang.hitch(this, function () {
this.lockCamera(false);
this.setSelected(null);
}), 100);
this._dragEvent(event);
this.dragObject = this.dragLatOrigin = this.dragLngOrigin = this.dragLatLast = this.dragLngLast = null;
}
});
And finally, _dragEvent is called to ensure that the final coordinates are the actual coordinates the mouse event finished with (and not the latest mousemove call):
_dragEvent: function (event) {
// summary:
// Helper function for moving drag object
var latDiff = event.getLatitude() - this.dragLatLast;
var lngDiff = event.getLongitude() - this.dragLngLast;
if (!(latDiff == 0 && lngDiff == 0)) {
this.addPolyCoords(this.dragObject, [latDiff, lngDiff]);
Log.info('Moved ' + latDiff + ', ' + lngDiff);
}
},
The mousemove callback isn't too important and can actually be ignored - the only reason I use it is to show the polygon moving as the user moves their mouse. Removing it will result in the object being moved when they lift their mouse up.
Hopefully this incredibly long answer gives you some insights into how to implement dragging in the Google Earth API. And I also plan to release my library in the future when I've ironed out the kinks :)