Zoom and position management in MapKit.js - javascript

I've created following map using MapKit.js, with hundred of custom annotations, clustering (yellow dots) and callout popup on annotation click.
What I want to do, when clicking on the popup link, is simply to zoom in one step and center view on the clicked annotation (in a responsive context).
In Google Maps, that I'm used to, you simply position map by it's center and zoom level.
In MapKit.js, you use a center/region combo, and honestly I can't understand how this works.
Official doc is unclear to me, and I wasn't able to find really enlightling ressource.
If someone could explain to me how we are supposed to manage zoom level using center / region combo, it would be really appreciated.
Thanks :-)
[EDIT]
This center/region thing still doesn't make sense to me, so I've decided to override MapKit.js with a zoom feature.
Thanks to this post, I've manage to implement the zoom calculation, which seems to be ok.
I need now to implement the set zoom action.
No success yet, this math things are so far now ^^
Any help is highly welcomed :-)
Function:
function MapKitJsZoom(map) {
var LN2 = 0.6931471805599453; // ???
var WH = 256; // World Height
var WW = 256; // World Width
var MAX = 21; // Max zoom level
// GET CURRENT ZOOM.
var latToRad = function (lat) {
var sin = Math.sin(lat * Math.PI / 180);
var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
};
var zoom = function (mapPx, worldPx, fraction) {
return (Math.log(mapPx / worldPx / fraction) / LN2);
};
this.get = function () {
var bounds = map.region.toBoundingRegion();
var latFraction = (latToRad(bounds.northLatitude) - latToRad(bounds.southLatitude)) / Math.PI;
var latZoom = zoom(map.element.clientHeight, WH, latFraction);
var lngDiff = bounds.eastLongitude - bounds.westLongitude;
var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
var lngZoom = zoom(map.element.clientWidth, WW, lngFraction);
return Math.round(Math.min(latZoom, lngZoom, MAX));
};
// SET CURRENT ZOOM
this.set = function (zoom) {
// TODO
// I need to calculate latitude and longitude deltas
// that correspond to required zoom based on viewport size
// (map.element.clientWidth and map.element.clientHeight)
map.region.span = new mapkit.CoordinateSpan(latitudeDelta, longitudeDelta);
};
}
Usage:
var map = new mapkit.Map("map");
map.zoom = new MapKitJsZoom(map);
map.addEventListener('region-change-end', function () {
console.log(map.zoom.get());
});

There are two methods of accomplishing this:
1) set center then change zoom level
var newCenter = new mapkit.Coordinate(37.792446, -122.399360);
map._impl.zoomLevel--;
map.setCenterAnimated(newCenter, true);
2) set region using center and span (delta in degress)
var newCenter = new mapkit.Coordinate(37.792446, -122.399360);
var span = new mapkit.CoordinateSpan(.01);
var region = new mapkit.CoordinateRegion(newCenter, span);
map.setRegionAnimated(region)

Related

Initiate d3 map over certain area given latitude and longitude

I am building a map in d3 and basing it off of this codepen by Andy Barefoot: https://codepen.io/nb123456/pen/zLdqvM?editors=0010. I want to modify the initiateZoom() function so that if I set the lat/lon coordinates for a box surrounding say Ohio, the map will initialize its panning to be over Ohio.
function initiateZoom() {
minZoom = Math.max($("#map-holder").width() / w, $("#map-holder").height() / h);
maxZoom = 20 * minZoom;
zoom
.scaleExtent([minZoom, maxZoom])
.translateExtent([[0, 0], [w, h]])
;
midX = ($("#map-holder").width() - minZoom * w) / 2;
midY = ($("#map-holder").height() - minZoom * h) / 2;//These are the original values
var swlat = 32;
var swlon = -82;
var nelat = 42;
var nelon = -72;
var projectCoordinates = projection([(swlat+nelat)/2, (swlon+nelon)/2]);
/*This did not work
var midX = minZoom*(w-(swlat+nelat)/2) - ($("#map-holder").width()-(swlat+nelat)/2);
var midY = minZoom*(h-(swlon+nelon)/2) - ($("#map-holder").height()-(swlon+nelon)/2);*/
/*Neither did this
var midX = minZoom*(w-projectCoordinates[0])-($("#map-holder").width()-projectCoordinates[0]);
var midY = minZoom*(h-projectCoordinates[1])-($("#map-holder").height()-projectCoordinates[1]);*/
svg.call(zoom.transform, d3.zoomIdentity.translate(midX, midY).scale(minZoom));
}
The idea behind the original approach was to:
1: Get the current pixel display of the map
2: Get the new pixel distance from the map corner to the map point after zoom has been applied
3: The pixel distance of the center of the container to the top of the container
4: subtract the values from 2 and 3
The original post was trying to translate the map so that it would initialize the zoom and pan over the center of the map. I tried to modify this approach first by directly substituting the lat/lon values into the above equations. I also tried first transforming the lat/lon values using the projection and then substituting those values in, with little success. What do I need to do in order to get my desired result?
Setting a translateExtent could be a bad idea because it depends on the zoom scale.
The following replacement works.
function initiateZoom() {
// Define a "minzoom" whereby the "Countries" is as small possible without leaving white space at top/bottom or sides
minZoom = Math.max($("#map-holder").width() / w, $("#map-holder").height() / h);
// set max zoom to a suitable factor of this value
maxZoom = 20 * minZoom;
// set extent of zoom to chosen values
// set translate extent so that panning can't cause map to move out of viewport
zoom
.scaleExtent([minZoom, maxZoom])
.translateExtent([[0, 0], [w, h]])
;
var swlat = 32;
var swlon = -82;
var nelat = 42;
var nelon = -72;
var nwXY = projection([swlon, nelat]);
var seXY = projection([nelon, swlat]);
var zoomScale = Math.min($("#map-holder").width()/(seXY[0]-nwXY[0]), $("#map-holder").height()/(seXY[1]-nwXY[1]))
var projectCoordinates = projection([(swlon+nelon)/2, (swlat+nelat)/2]);
svg.call(zoom.transform, d3.zoomIdentity.translate($("#map-holder").width()*0.5-zoomScale*projectCoordinates[0], $("#map-holder").height()*0.5-zoomScale*projectCoordinates[1]).scale(zoomScale));
}

How to create this Canvas Animation JS

Does someone know how is this animation build, which js framework is used or something that can help me get to know to recreate something similar?
Banner BG animation ---> https://envylabs.com/
Thanks in advance!
I can't tell with what library this animation was build with, because it is hidden in React bundled code, but I can show you a way to do something similar with Paper.js.
By looking at the animation, it seems that rules are:
a circle is influenced by mouse pointer if it is under a certain distance from it
the closest to the mouse pointer a circle is:
the bigger it becomes
the farther from the window center it goes.
Here is a Sketch implementing this.
//
// CONSTANTS
//
// user defined
var ROWS_COUNT = 10; // number of rows in the grid
var COLUMNS_COUNT = 10; // number of columns in the grid
var MOUSE_INFLUENCE_RADIUS = 350; // maximal distance from mouse pointer to be influenced
var INFLUENCE_SCALE_FACTOR = 1; // maximal influence on point scale
var INFLUENCE_POSITION_FACTOR = 15; // maximal influence on point position
// computed
var STEP_X = view.bounds.width / COLUMNS_COUNT;
var STEP_Y = view.bounds.height / ROWS_COUNT;
var RADIUS = Math.min(STEP_X, STEP_Y) * 0.1;
//
// ITEMS
//
// create a circle for each points in the grid
var circles = [];
for (var i = 0; i < COLUMNS_COUNT; i++)
{
for (var j = 0; j < COLUMNS_COUNT; j++)
{
var gridPoint = new Point((i + 0.5) * STEP_X, (j + 0.5) * STEP_Y);
circles.push(new Path.Circle({
center : gridPoint,
radius : RADIUS,
fillColor : 'black',
// matrix application is disabled in order to be able to manipulate scaling property
applyMatrix: false,
// store original center point as item custom data property
data : {gridPoint: gridPoint}
}));
}
}
//
// EVENTS
//
function onMouseMove(event)
{
for (var i = 0; i < circles.length; i++)
{
var circle = circles[ i ];
var gridPoint = circle.data.gridPoint;
var distance = event.point.getDistance(gridPoint);
// only influence circles that are in mouse influence zone
if (distance <= MOUSE_INFLUENCE_RADIUS)
{
var influence = 1 - distance / MOUSE_INFLUENCE_RADIUS;
// the closest the circle is from the mouse pointer
// the bigger it is
circle.scaling = 1 + influence * INFLUENCE_SCALE_FACTOR;
// the farthest it is from view center
circle.position = gridPoint + (gridPoint - view.center).normalize(influence * INFLUENCE_POSITION_FACTOR);
}
else
{
// reset circle state
circle.scaling = 1;
circle.position = gridPoint;
}
}
}
Your question is too open.
But there are some libraries that will save you a lot of time:
D3.js
Processing.js
Paper.js
There are a lot more, but it depends on what you need.

add padding to google maps bounds.contains()

I have a sidebar which shows the names of the markers in the current map view of a google map. The sidebar contents change as the map gets moved:
google.maps.event.addListener(map, 'bounds_changed', function() {
document.getElementById("list").innerHTML = "";
var mklen = mkrs.length,
a = 0,
bnds = map.getBounds();
for (a; a < mklen; a++) {
var themk = mkrs[a];
if (bnds.contains(themk.getPosition())) {
var myli = document.createElement("li");
myli.innerHTML = themk.title;
document.getElementById("list").appendChild(myli);
}
}
});
That's working OK, but the thing is that the bounds.contains() is very strict - if just the bottom tip of the marker is on the map (ie, you can't see 99% of it) it gets listed on the sidebar. What I'd like is to have just the markers that are completely shown pass that test.
There are a couple of approaches that I can think of and I can't believe that nobody else has come up against this problem, so I'm wondering if there is a preference out of the following:
take the bounds and recalculate them to be smaller than the actual bounds and use those new bounds for the bounds.contains() test
calculate where the edges of the marker icons are (I guess using fromDivPixelToLatLng) then check that both the ne AND sw corners are within the bounds and if so, list the item
Before you ask, I haven't tried either of those - I'm more looking for advice on which would be best or even possible, or if there is another way to do this. Here's a fiddle demonstrating the issue, in case it clarifies
In case anybody finds this later, I ended up recalculating the bounds - it seemed to be the approach that involved the least overhead. Here's the function:
function paddedBounds(npad, spad, epad, wpad) {
var SW = map.getBounds().getSouthWest();
var NE = map.getBounds().getNorthEast();
var topRight = map.getProjection().fromLatLngToPoint(NE);
var bottomLeft = map.getProjection().fromLatLngToPoint(SW);
var scale = Math.pow(2, map.getZoom());
var SWtopoint = map.getProjection().fromLatLngToPoint(SW);
var SWpoint = new google.maps.Point(((SWtopoint.x - bottomLeft.x) * scale) + wpad, ((SWtopoint.y - topRight.y) * scale) - spad);
var SWworld = new google.maps.Point(SWpoint.x / scale + bottomLeft.x, SWpoint.y / scale + topRight.y);
var pt1 = map.getProjection().fromPointToLatLng(SWworld);
var NEtopoint = map.getProjection().fromLatLngToPoint(NE);
var NEpoint = new google.maps.Point(((NEtopoint.x - bottomLeft.x) * scale) - epad, ((NEtopoint.y - topRight.y) * scale) + npad);
var NEworld = new google.maps.Point(NEpoint.x / scale + bottomLeft.x, NEpoint.y / scale + topRight.y);
var pt2 = map.getProjection().fromPointToLatLng(NEworld);
return new google.maps.LatLngBounds(pt1, pt2);
}
and you call it like this:
var padbnds = paddedBounds(50, 70, 100, 30);
specifying how much padding you want on the north, south, east and west edges of the map respectively

Draw automatically the first vertice of a path with Open Layers

I'd like to help the user to input an orientation for a segment with OpenLayers.
I have that form where user can input the bearing for a point, but I would like to help him by :
start drawing the first vertice of a segment on the map when the user clicks on a button, (that first vertice being a known point)
then the user just has to click for the second vertice, and bearing is computed automatically.
See the fiddle here or SO snippet below.
I'm almost done : I can compute the bearing when a segment is drawn. But there's an exception at the very end of the script : I can't get OL to draw automatically the first point of my segment.
Thank you to anyone who can help.
<script src="http://openlayers.org/api/OpenLayers.js"></script>
<body>
<div id="map" style="height: 500px"></div>
</body>
<script>
var CONSTANTS = {
MAP_FROM_PROJECTION: new OpenLayers.Projection("EPSG:4326"), // Transform from WGS 1984
MAP_TO_PROJECTION: new OpenLayers.Projection("EPSG:900913") // to Spherical Mercator Projection
};
function radians(n) {
return n * (Math.PI / 180);
}
function degrees(n) {
return n * (180 / Math.PI);
}
function computeBearing(startLat, startLong, endLat, endLong) {
startLat = radians(startLat);
startLong = radians(startLong);
endLat = radians(endLat);
endLong = radians(endLong);
var dLong = endLong - startLong;
var dPhi = Math.log(Math.tan(endLat / 2.0 + Math.PI / 4.0) / Math.tan(startLat / 2.0 + Math.PI / 4.0));
if (Math.abs(dLong) > Math.PI) {
if (dLong > 0.0) dLong = -(2.0 * Math.PI - dLong);
else dLong = (2.0 * Math.PI + dLong);
}
return (degrees(Math.atan2(dLong, dPhi)) + 360.0) % 360.0;
}
map = new OpenLayers.Map("map");
map.addLayer(new OpenLayers.Layer.OSM());
map.setCenter(new OpenLayers.LonLat(3, 47).transform(CONSTANTS.MAP_FROM_PROJECTION, CONSTANTS.MAP_TO_PROJECTION), 6);
var lineLayer = new OpenLayers.Layer.Vector("Line Layer");
map.addLayers([lineLayer]);
var lineControl = new OpenLayers.Control.DrawFeature(lineLayer, OpenLayers.Handler.Path, {
handlerOptions: {
maxVertices: 2,
freehandMode: function(evt) {
return false;
}
},
featureAdded: function(feature) {
var drawnLinePoints = feature.geometry.getVertices();
var lonlat1 = drawnLinePoints[0].transform(CONSTANTS.MAP_TO_PROJECTION, CONSTANTS.MAP_FROM_PROJECTION);
var lonlat2 = drawnLinePoints[1].transform(CONSTANTS.MAP_TO_PROJECTION, CONSTANTS.MAP_FROM_PROJECTION);
var bearingValue = computeBearing(lonlat1.y, lonlat1.x, lonlat2.y, lonlat2.x);
console.log(bearingValue);
}
});
map.addControl(lineControl);
lineControl.activate();
var handler;
for (var i = 0; i < map.controls.length; i++) {
var control = map.controls[i];
if (control.displayClass === "olControlDrawFeature") {
handler = control.handler;
break;
}
}
// Here I have an exception in the console : I would like
// OL to draw hat point automatically.
handler.addPoint(new OpenLayers.Pixel(50, 50));
</script>
OpenLayers.Handler.Path.addPoint works on OpenLayers.Pixel, not OpenLayers.LonLat:
/**
* Method: addPoint
* Add point to geometry. Send the point index to override
* the behavior of LinearRing that disregards adding duplicate points.
*
* Parameters:
* pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
*/
addPoint: function(pixel) {
this.layer.removeFeatures([this.point]);
var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
this.point = new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
);
this.line.geometry.addComponent(
this.point.geometry, this.line.geometry.components.length
);
this.layer.addFeatures([this.point]);
this.callback("point", [this.point.geometry, this.getGeometry()]);
this.callback("modify", [this.point.geometry, this.getSketch()]);
this.drawFeature();
delete this.redoStack;
}
I actually see no good way of achieving this other than adding an addPointByLonLat method:
OpenLayers.Handler.Path.prototype.addPointByLonLat = function(lonLat) {
this.layer.removeFeatures([this.point]);
this.point = new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
);
this.line.geometry.addComponent(
this.point.geometry, this.line.geometry.components.length
);
this.layer.addFeatures([this.point]);
this.callback("point", [this.point.geometry, this.getGeometry()]);
this.callback("modify", [this.point.geometry, this.getSketch()]);
this.drawFeature();
delete this.redoStack;
};
Or subclass as your own handler class (propbably cleaner).
Notes:
addPoint is not an API method (so addPointByLonLat is also not). This may result in problem on version changes.
Don't use the compressed/minified JS in development and check docs on methods you use.
Next time consider asking on https://gis.stackexchange.com/.
Consider asking for a code review on your JS.
You can also use insertXY(x,y) function in order to insert a point with geographic coordinates
http://dev.openlayers.org/docs/files/OpenLayers/Handler/Path-js.html#OpenLayers.Handler.Path.insertXY
lonlat = new OpenLayers.LonLat(1,45);
lonlat.transform(CONSTANTS.MAP_FROM_PROJECTION,CONSTANTS.MAP_TO_PROJECTION);
handler.createFeature(new OpenLayers.Pixel(100, 100));
handler.insertXY(lonlat.lon,lonlat.lat);
handler.drawFeature();
You can check it here with a fork of your original jsfiddle : http://jsfiddle.net/mefnpbn2/
See this fiddle for the solution.
tldr :
// draw the first point as I needed, as if a user has clicked on the map
handler.modifyFeature(new OpenLayers.Pixel(50, 50), true);
// draw a first point on the map (not clicked).
// This will be the initial point where the "cursor" is on the map, as long as
// the user hasn't hovered onto the map with its mouse. This make the blue
// line showing current segment to appear, without this segment is drawn but
// no feedback is given to the user as long as he hasn't clicked.
handler.addPoint(new OpenLayers.Pixel(50, 50)); //

How to implement a geodesic option for the Scale Bar add-in in OpenLayers 2?

I am using the Scale Bar Add-in from OpenLayers 2 and it doesn't have a geodesic option.
I know that the Scale Line has a geodesic option, but considering that the Scale Bar has a wider variety of options, it would be nice to introduce he geodesic option in this one too.
Could anyone help me with this? How could I introduce a geodesic option in the Scale Bar Add-in?
Thank you
I think I solved it.
First of all, I added a geodesic option, so the user can choose if he wantss the geodesic measures or not.
Second, I created this function (based on the ScaleLine):
getGeodesicRatio: function() {
var res = this.map.getResolution();
if (!res) {
return;
}
var curMapUnits = this.map.getUnits();
var inches = OpenLayers.INCHES_PER_UNIT;
// convert maxWidth to map units
var maxSizeData = this.maxWidth * res * inches[curMapUnits];
var geodesicRatio = 1;
if(this.geodesic) {
var maxSizeGeodesic = (this.map.getGeodesicPixelSize().w ||
0.000001) * this.maxWidth;
var maxSizeKilometers = maxSizeData / inches["km"];
geodesicRatio = maxSizeGeodesic / maxSizeKilometers;
}
return geodesicRatio;
},
Third, I inserted the geodesic ratio in other functions:
"getComp"
var ppdu = OpenLayers.DOTS_PER_INCH * system.inches[unitIndex]
/ this.getGeodesicRatio() / this.scale;
"setSubPros"
var ppdu = OpenLayers.DOTS_PER_INCH * system.inches[unitIndex]
/ this.getGeodesicRatio() / this.scale;
I hope this is useful.

Categories

Resources