Panning the map to certain extent javascript API - arcgis

I want to limit map extent to the initial extent of the map and limit user from panning more than certain extent.
I tried following but nothing has changed:
map = new Map( "map" , {
basemap: "gray",
center: [-85.416, 49.000],
zoom : 6,
logo: false,
sliderStyle: "small"
});
dojo.connect(map, "onExtentChange", function (){
var initExtent = map.extent;
var extent = map.extent.getCenter();
if(initExtent.contains(extent)){}
else{map.setExtent(initExtent)}
});

Just to flesh out Simon's answer somewhat, and give an example. Ideally you need two variables at the same scope as map:
initExtent to store the boundary of your valid extent, and
validExtent to store the last valid extent found while panning, so that you can bounce back to it.
I've used the newer dojo.on event syntax as well for this example, it's probably a good idea to move to this as per the documentation's recommendation - I assume ESRI will discontinue the older style at some point.
var map;
var validExtent;
var initExtent;
[...]
require(['dojo/on'], function(on) {
on(map, 'pan', function(evt) {
if ( !initExtent.contains(evt.extent) ) {
console.log('Outside bounds!');
} else {
console.log('Updated extent');
validExtent = evt.extent;
}
});
on(map, 'pan-end', function(evt) {
if ( !initExtent.contains(evt.extent) ) {
map.setExtent(validExtent);
}
});
});
You can do the same with the zoom events, or use extent-change if you want to trap everything. Up to you.

It looks like your extent changed function is setting the initial extent variable to the maps current extent and then checking if that extent contains the current extents centre point - which of course it always will.
Instead, declare initExtent at the same scope of the map variable. Then, change the on load event to set this global scope variable rather than a local variable. In the extent changed function, don't update the value of initExtent, simply check the initExtent contains the entire of the current extent.
Alternatively you could compare each bound of the current extent to each bound of the initExtent, e.g. is initExtent.xmin < map.extent.xmin and if any are, create a new extent setting any exceeded bounds to the initExtent values.
The only problem is these techniques will allow the initExtent to be exceeded briefly, but will then snap the extent back once the extent changed function fires and catches up.

I originally posted this solution on gis.stackexchange in answer to this question: https://gis.stackexchange.com/a/199366
Here's a code sample from that post:
//This function limits the extent of the map to prevent users from scrolling
//far away from the initial extent.
function limitMapExtent(map) {
var initialExtent = map.extent;
map.on('extent-change', function(event) {
//If the map has moved to the point where it's center is
//outside the initial boundaries, then move it back to the
//edge where it moved out
var currentCenter = map.extent.getCenter();
if (!initialExtent.contains(currentCenter) &&
event.delta.x !== 0 && event.delta.y !== 0) {
var newCenter = map.extent.getCenter();
//check each side of the initial extent and if the
//current center is outside that extent,
//set the new center to be on the edge that it went out on
if (currentCenter.x < initialExtent.xmin) {
newCenter.x = initialExtent.xmin;
}
if (currentCenter.x > initialExtent.xmax) {
newCenter.x = initialExtent.xmax;
}
if (currentCenter.y < initialExtent.ymin) {
newCenter.y = initialExtent.ymin;
}
if (currentCenter.y > initialExtent.ymax) {
newCenter.y = initialExtent.ymax;
}
map.centerAt(newCenter);
}
});
}
And here's a working jsFiddle example: http://jsfiddle.net/sirhcybe/aL1p24xy/

Related

How to export vuelayers map to png or jpeg?

How would I adapt #ghettovoice JSFiddle that saves a map to PDF to save the map to a JPEG or PNG? I have no idea how to attempt this problem so ideally if you know hoe to do it you can explain the logic behind it.
exportMap: function () {
var map = this.$refs.map
map.once('rendercomplete', function () {
var mapCanvas = document.createElement('canvas');
var size = map.getSize();
mapCanvas.width = size[0];
mapCanvas.height = size[1];
var mapContext = mapCanvas.getContext('2d');
Array.prototype.forEach.call(
document.querySelectorAll('.ol-layer canvas'),
function (canvas) {
if (canvas.width > 0) {
var opacity = canvas.parentNode.style.opacity;
mapContext.globalAlpha = opacity === '' ? 1 : Number(opacity);
var transform = canvas.style.transform;
// Get the transform parameters from the style's transform matrix
var matrix = transform
.match(/^matrix\(([^(]*)\)$/)[1]
.split(',')
.map(Number);
// Apply the transform to the export map context
CanvasRenderingContext2D.prototype.setTransform.apply(
mapContext,
matrix
);
mapContext.drawImage(canvas, 0, 0);
}
}
);
if (navigator.msSaveBlob) {
// link download attribuute does not work on MS browsers
navigator.msSaveBlob(mapCanvas.msToBlob(), 'map.png');
} else {
var link = document.getElementById('image-download');
link.href = mapCanvas.toDataURL();
link.click();
}
});
map.renderSync();
}
The problem was a combination of missing dependencies (namely FileSaver.js and fakerator.js) and a cross origin server block (CORS block) (Browsers automatically prevent httpRequests to a different domain name unless the server allows it). The first one is fixed by installing the packages while the second one is resolved by setting the crossOrigin Attribute of the ImageWMSLayer to null in my case but possibly to 'Anonymous' for other sources. Hope this helped someone else :)

Titanium geolocation distanceFilter property is not working as expected

Need a help here for geolocation API - even after using Titanium.Geolocation.distanceFilter = 10; 10 in meter callback function is getting triggered randomly without moving here and there. Any idea what is wrong here ?
function Geolocate() {
var self = this;
// The callback function we should call when location is finally determined
this.locationReceivedCallback = function () {};
// Check if location event is already triggered or not
this.isLocationEventRegister = false;
// The function that unregisters the location event
this.unregisterLocationEvent = function () {
this.isLocationEventRegister = false;
Ti.Geolocation.removeEventListener('location', self.locationReceivedCallback);
};
}
Geolocate.prototype.init = function () {
// Setting up some preference values
Titanium.Geolocation.distanceFilter = 10; //The minimum change of position (in meters) before a 'location' event is fired.
if (deviceDetect.isIos()) {
Titanium.Geolocation.accuracy = Titanium.Geolocation.ACCURACY_BEST; // Using this value on Android enables legacy mode operation, which is not recommended.
} else {
Titanium.Geolocation.accuracy = Titanium.Geolocation.ACCURACY_LOW; //Using this value on Android enables simple mode operation.
}
};
Geolocate.prototype.getCurrentPosition = function (callback) {
this.isLocationEventRegister = true;
this.locationReceivedCallback = callback;
Titanium.Geolocation.addEventListener("location", this.locationReceivedCallback);
};
Geolocate.prototype.locationServicesAreAvailable = function () {
if (Titanium.Geolocation.locationServicesEnabled) {
return true;
} else {
return false;
}
};
Geolocate.prototype.cancelLocationRequest = function () {
this.unregisterLocationEvent();
};
module.exports = Geolocate;
Actual scenario is whenever I clicked on get location its gives me current location. then i tried to drag it map or image view for nearest place. Suddenly my view go current location. This is happening because of call back ? How to get rid off this ?
It's not a problem to do with your code, it is simply GPS in-accuracy.
GPS accuracy is (almost) never better than 10 meters, meaning it can be 10 meters off. When it recalculates your position it can be 10 meters down the line, so even if you're perfectly still with perfect GPS accuracy, you still might have differences of about 10 meters.
That said, you probably don't have best GPS accuracy when sitting behind a computer in a building, you probably have closer to 30-45 meters accuracy. Meaning every recalculation can easily be 10 meters differently.
Your solution would actually be rate-limit on top of using this property. The property will make sure it will not trigger multiple times per second, and then you can, in your own code, rate limit what you do with it.

EaselJs shape hitTest unable to set alpha or visiblity

From the sample I created
I am unable to set the circle's alpha / visiblity when mousedown draw line. But I am able to console log it when its detect hitTest.. Is there advise in this?
Below is the block of codes:
var canvas, stage;
var drawingCanvas;
var oldPt;
var oldMidPt;
var title;
var color;
var stroke;
var colors;
var index;
var rect, circle1;
var currMidPt;
$(document).ready(function() {
init();
});
function init() {
canvas = document.getElementById("canvas");
index = 0;
colors = ["#828b20", "#b0ac31", "#cbc53d", "#fad779", "#f9e4ad", "#faf2db", "#563512", "#9b4a0b", "#d36600", "#fe8a00", "#f9a71f"];
//check to see if we are running in a browser with touch support
stage = new createjs.Stage(canvas);
stage.autoClear = false;
stage.enableDOMEvents(true);
createjs.Touch.enable(stage);
createjs.Ticker.setFPS(24);
createjs.Ticker.on("tick", tick);
drawingCanvas = new createjs.Shape();
stage.addEventListener("stagemousedown", handleMouseDown);
stage.addEventListener("stagemouseup", handleMouseUp);
title = new createjs.Text("Click and Drag to draw", "36px Arial", "#777777");
title.x = 300;
title.y = 200;
rect = new createjs.Shape();
rect.graphics.beginFill("#000").drawRect(0, 0, stage.canvas.width, stage.canvas.height);
var container = new createjs.Container();
container.x = 0;
container.y = 0;
stage.addChild(container, title);
stage.addChild(drawingCanvas);
circle1 = new createjs.Shape();
circle1.graphics.beginFill("#990000").drawCircle(120,120,40);
container.addChild(circle1);
stage.update();
}
function handleMouseDown(event) {
if (!event.primary) { return; }
if (stage.contains(title)) {
stage.clear();
stage.removeChild(title);
}
color = colors[(index++) % colors.length];
stroke = Math.random() * 30 + 10 | 0;
oldPt = new createjs.Point(stage.mouseX, stage.mouseY);
oldMidPt = oldPt.clone();
stage.addEventListener("stagemousemove", handleMouseMove);
}
function handleMouseMove(event) {
if (!event.primary) { return; }
var midPt = new createjs.Point(oldPt.x + stage.mouseX >> 1, oldPt.y + stage.mouseY >> 1);
drawingCanvas.graphics.clear().setStrokeStyle(stroke, 'round', 'round').beginStroke(color).moveTo(midPt.x, midPt.y).curveTo(oldPt.x, oldPt.y, oldMidPt.x, oldMidPt.y);
oldPt.x = stage.mouseX;
oldPt.y = stage.mouseY;
oldMidPt.x = midPt.x;
oldMidPt.y = midPt.y;
currMidPt = midPt;
if(circle1.hitTest(currMidPt.x, currMidPt.y)) {
console.log('test');
circle1.alpha = 0.6;
circle1.visible = false;
}
stage.update();
}
function tick(event) {
// console.log(ndgmr.checkPixelCollision(drawingCanvas,circle1,0,false));
stage.update(event);
}
function handleMouseUp(event) {
if (!event.primary) { return; }
stage.removeEventListener("stagemousemove", handleMouseMove);
}
The main reason this doesn't work is because you are using a stage that never clears itself. This gives you the benefit of a "paint brush", since it just adds new curves as you draw, but the circle can never be removed, since it is painted on to the canvas. If you change the alpha, it just draws on top of the current circle. This is also why your circle gets all aliased, as it constantly draws on top of itself, multiplying the alpha.
Here is a quick edit that shows the circle moving its x position instead of adjusting the alpha. Any time you roll over the original position it will move 10 pixels to the right.
http://codepen.io/lannymcnie/pen/redrgo?editors=0010
The hitTest code you have used is not correct though, since it always checks the x/y position of the pen against the local coordinates of the circle -- this means the position of the circle doesn't matter. To fix this, you need to find the local coordinates instead:
currMidPt = circle1.globalToLocal(midPt.x, midPt.y);
Here is an updated fiddle showing that behaviour: http://codepen.io/lannymcnie/pen/grejVg?editors=0010
However, to get the effect you are probably looking for, you have to take a different approach. Instead of using the stage auto-clear, use a cached Shape, and update the cache when you draw. This will provide the paint effect, but let the rest of the stage get updated as usual.
// Remove this!
stage.autoClear = false;
// Cache the drawingCanvas once after creating it
drawingCanvas = new createjs.Shape();
drawingCanvas.cache(0,0,canvas.width,canvas.height);
// After drawing, update the cache. This is at the end of the mousemove function.
// Use "source-over" to apply the new contents on top, instead of clearing the cache.
drawingCanvas.updateCache("source-over");
stage.update();
I also made a few other changes:
Removed the Ticker listener that updates the stage. Your mousemove already updates the stage when the contents change. If you want to reintroduce the ticker update for other content, then remove the stage.update() calls everywhere else, as they are redundant.
Moved the drawingCanvas below the circle/container. This just makes sure the circle is always visible for the demo
Here is a final demo with these changes:
http://codepen.io/lannymcnie/pen/NNYLPX?editors=0010
Hope that helps!

Use Dojo boxConstrainedMoveable to constrain movable div to window

I have a div, to which I applied Dojo dojo/dnd/Moveable. But, I'd like to prevent the user from dragging the div offscreen. So, I think I need to implement dojo/dnd/move/boxConstrainedMoveable.
I'm starting with this:
var dnd = new Moveable(this.domNode, {
'handle': this.titleNode
});
There's a similar SO question here:
Constrain a moveable object in Dojo. Applying that answer, I get something like this:
var dnd = new move.boxConstrainedMoveable(
'handle': this.titleNode
constraints: {
l: 0,
t: 20,
w: 500,
h: 500
},
within: true
);
But, I just can't understand how the bounding box works. I simply want the div to stay inside the window. I've tried implementing a few things with the window box, the div's margin box. Nothing's worked, and all I've made is a big mess.
I read the docs here:
http://dojotoolkit.org/api/?qs=1.9/dojo/dnd/move.boxConstrainedMoveable
Has anyone done this with Dojo? I'd be very appreciate of an example.
I looked up some old code I have and I did implement this type of movable once. This was written against Dojo 1.7, so things may have changed in 1.9. Fiddle demonstration: https://jsfiddle.net/4ev1daqr/26/
The main difference between your attempted solution and this is that the constraints property in the moveable needs to be a function rather than a static bounding box. When using the boxConstrainedMoveable module, the static bounding box should be assigned to a box property, rather than the constraints property.
This is actually a nice design, IMHO, because it allows the constraints to react to changes in application state, e.g. hiding a sidebar or moving a splitter, but it does make the simple case a bit more difficult to get working.
define(["dojo/_base/declare",
"dojo/dnd/move",
"dojo/dom",
"dojo/_base/window",
"dojo/dom-style",
"dojo/dom-geometry",
],
function (declare, move, dom, win, domStyle, domGeom) {
return declare( "my/dnd/move/BodyConstrainedMoveable", [move.constrainedMoveable], {
markupFactory: function(params, node){
return new this(node, params);
},
constructor: function(node, params) {
// Constrain the node to be within the body
this.constraints = function() {
var n = win.body(),
s = domStyle.getComputedStyle(n),
mb = domGeom.getMarginBox(n, s);
if ( this.node ) {
var menubox = domGeom.getMarginBox(this.node);
mb.w -= menubox.w;
mb.h -= menubox.h;
}
return mb;
};
}
})});

ExtJs 4 : Tree grid panel filter

I am using ExtJs 4 with a Tree panel on west region and TreeGrid panel on center region. Is there any way to filter the TreeGrid panel(center region) on selection of the treepanel(west) ??
I tried the following but no luck :
Ext.define('MyApp.view.MyViewport', {
extend: 'MyApp.view.ui.MyViewport',
initComponent: function() {
var me = this;
me.callParent(arguments);
me.down('#westTreePanel').getSelectionModel().on('selectionchange',me.CenterTreeFilter,me);
}, //end of initComponent
CenterTreeFilter: function(){
var selection = this.down('#westTreePanel').getView().getSelectionModel().getSelection()[0];
var centerTreeGrid = this.down('#centerTreeGrid');
console.log(selection.data.text);
centerTreeGrid.store.filterBy(function(rec, id){
console.log(rec);
return (rec.store("text") == selection.data.text);
});
console.log("sub store : " + this.down('#centerTreeGrid').getStore().storeId);
}
});
After days of fighting with this issue, I was finally able to get the functionality, albeit in a not so satisfying way. Also, only leaf nodes are currently hidden.
filtering all nodes that don't mention "text":
t.getRootNode().cascadeBy(function(n){
if (!n.hasChildNodes() &&
n.raw && n.raw.text.toLowerCase().indexOf(text.toLowerCase()) < 0) {
toRemove.push({ node: n, parent: n.parentNode });
}
});
To restore later, run:
function restoreTrees() {
for (var n in toRemove) {
toRemove[n].parent.appendChild(toRemove[n].node);
}
toRemove = [];
}
There are many flaws with this solution. Including that the restored tree will probably have a different ordering for their nodes. But hey, at least this is some progress.
Would love to see a better one! (Had it working beautifully in Ext JS 3, but now these darn nodes don't have a .ui.hide() function any more).
i've checked their example http://dev.sencha.com/deploy/ext-4.0.2a/examples/tree/treegrid.html, in fact the issue here is that the store for the treeGrid is a tree store which doesn;t have the method filterBy , the method filterBy is defined in ext.data.store and treeStore extends ext.data.abstractStore.. as i see it you have to apply your filters diferently, using the the filters config for the treeStore. You can define your filter and set the filterOnLoad on true and instead of calling the filterBy method do centerTreeGrid.store.fireEvent('load',selection). I hope this helps you
Edit
I haven't used filters for tree stores but i think you can do something like this
var treeFilter = new Ext.util.Filter({
filterFn: function(rec) {
console.log(rec);
return (rec.store("text") == selection.data.text);
});
And assign the filter to the treeStore in the initComponent
centerGrid.store.filters.add(treeFilter);
centerGrid.store.filterOnLoad = true;
And in the CenterTreeFilter function
centerGrid.store.fireEvent('load',selection);
P.s the code is untested and probably it will need some modifications, but i think this is the way to do it.