Properly Remove LoadMask Once GeoExt MapPanel is Completely Loaded - javascript

I would like to have a mask over the whole page which does not get removed until the entire page has completely loaded. More specifically, I have a map created with OpenLayers and GeoExt and I am trying to use an ExtJS loadMask. However, I have not been able to find any other way of doing this other than using a manual setTimeout which I do not want to use. I'd much rather have the mask removed only if the page is completely loaded. I have tried to use the 'loadend' event on the openLayers map as well as windows.onload etc:
My map and loadMask config:
var mask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
mask.show();
Ext.onReady(function() {
var options = {
controls: [new OpenLayers.Control.Navigation()],
maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34),
units: 'm',
allOverlays: false
}
var map = new OpenLayers.Map(options);
map.events.register("loadend", map , function() {
mask.hide(); alert('howdy');
});
var mapPanel = new GeoExt.MapPanel({
title: "Map",
map: map,
id: 'mapPanel',
layerStore: map.layers,
//Set the map to be centered at specified longitude/latitude, transform our layers (SRID=4326) to display properly on Google
//base layers (SRID=900913)
center: new OpenLayers.LonLat(95.20, 30.34).transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913")),
zoom: 7,
region: "center",
tbar: [measureLength, '-', measureArea, '-'],
bbar: [
{
xtype: "label",
text: "Scale = 1 : "
}
],
items: [{
xtype: "gx_zoomslider",
vertical: true,
height: 300,
aggressive: true,
x: 10,
y: 20,
plugins: new GeoExt.ZoomSliderTip()
}]
});
It seems this event never happens as I never get an alert message. I really really want to get this working, other attempts were:
window.onload = mask.hide();
after the Ext.onReady and at the end of the </body> tag, but then the mask is hidden way before the map is done loading. Could anyone share some insight, I'd really appreciate it!

Add the event 'onMapReady' just after the items like so:
onMapReady: function() {
Ext.getBody().unmask();
}

Related

How I can change map in the datamaps to an image of world map?

I want to change the map in the datamaps to a svg image. so that I can change it to different map as needed at a later stage. Is it possible to do that or do I need to change the code in the datamap to change the map to a different map ? or is there way I can hide some of the country in the world map ?
I have been able to solve the issue using the dataURL and get the desired results
this.worldMap = new Datamap({
element: this.worldMapElementRef.nativeElement,
responsive: true,
scope: 'world',
aspectRatio: 0.60,
fills: {
defaultFill: '#22496e',
'color-type-1': '#ffdd00'
},
geographyConfig: {
dataUrl: '../../../../assets/world.topo.json',
popupOnHover: true,
highlightOnHover: false,
borderWidth:1,
borderOpacity: 1,
borderColor: '#22496e',
}
});

Using Openlayers 3 and proj4js with RequireJS

I am experiencing an issue when using Openlayers 3 and proj4js with RequireJS.
Using a standard JavaScript file and html, I have working code to display a map and show the coordinates in EPSG:27700 using an Openlayers mouse position control.
When I make use of RequireJS (see the code below), the code fails due to the proj.get returning "undefined". The error in Chrome would suggest that Require is throwing the error.
I have tried to use shims, but this didn't work and I am not convinced that this is the right approach. Can anyone advise me how to get this working?
require.config({
baseUrl: './',
paths: {
'domReady': '../lib/domReady',
'openlayers': '../lib/ol',
'proj4': '../lib/proj4'
}
});
require([
'domReady',
'openlayers',
'proj4'
], function (domReady, openLayers, proj4) {
"use strict";
function getLayers() {
var baseLayer = new openLayers.layer.Tile({
source: new openLayers.source.OSM()
});
return [baseLayer];
};
domReady(function () {
proj4.defs('EPSG:27700', '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs');
openLayers.proj.get("EPSG:27700").setExtent([0, 0, 700000, 1300000]);
var mousePositionControl = new openLayers.control.MousePosition({
coordinateFormat: openLayers.coordinate.createStringXY(4),
projection: 'EPSG:27700'
});
var map = new openLayers.Map({
target: 'map',
layers: getLayers(),
controls: [mousePositionControl],
view: new openLayers.View({
projection: 'EPSG:27700',
center: [300000, 500000],
resolutions: [4500, 3200, 2400, 1600, 800, 400, 200, 100, 50, 25, 10, 5, 2.5, 1, 0.5, 0.25, 0.125, 0.0625],
zoom: 3,
minResolution: 25,
maxResolution: 800
})
});
});
});
You have to tell OpenLayers how to find proj4.js first:
openLayers.proj.setProj4(proj4);
The above snippet requires OpenLayers >= v3.13.0, and assumes a setup like in the code snippet in the question.

Openlayers projection with OSM style maps and a GeoJSON vector layer

Note: I know there's another question similar to this but it hasn't been answered and I need to know how mixed projections can be dealt with with GeoJSON and OSM.
I'm so confused. I was using the OSMDroid API on Android for mapping and wanted to replicate it using OpenLayers and GeoExt, but I've got a projection problem with including GeoJSON nodes and action events.
My tile set is OSM based, and is hosted on the same Web server as this HTML/JS. See it all below. I realize my boundaries aren't working, and my projections might be completely wrong. I've been testing different combinations.
The problem is my map displays correctly and is centered fine. However:
My GeoJSON feature nodes are way off the map. They're in a different projection long/lat, but I don't know how to set or convert GeoJSON long/lat to the current map projection.
My mapCtrl doesn't work. When I click it the lonlat is another projection (the OSM projection coords) and I can't seem to convert them)
Any tips on how extent/bounds actually work would be greatly appreciated
Can someone please help with a bit of projection advice? Sigh... I'm not patient enough for this.
Here's my full JS, as is:
var mapPanel, store, gridPanel, mainPanel, nodePop, mapPop;
Ext.onReady(function() {
var map, mapLayer, vecLayer;
var lon = -70.885610;
var lat = 38.345822;
var zoom = 17;
var maxZoom = 18;
var toProjection = new OpenLayers.Projection("EPSG:4326");
var fromProjection = new OpenLayers.Projection("EPSG:900913");
var extent = new OpenLayers.Bounds(-1.32,51.71,-1.18,51.80).transform(fromProjection, toProjection);
// Setup the node layer feature store and push it all into a vector layer
vecLayer = new OpenLayers.Layer.Vector("vector");
store = new GeoExt.data.FeatureStore({
layer: vecLayer,
fields: [
{name: 'name', type: 'string'},
{name: 'status', type: 'string'}
],
proxy: new GeoExt.data.ProtocolProxy({
protocol: new OpenLayers.Protocol.HTTP({
url: "data/sa.json",
format: new OpenLayers.Format.GeoJSON()
})
}),
autoLoad: true
});
// Setup the basic map layer using OSM style tile retreival to pull tiles
// from the same server hosting this service
map = new OpenLayers.Map(
'map', {
controls:[
new OpenLayers.Control.Navigation(),
new OpenLayers.Control.PanZoomBar(),
new OpenLayers.Control.Attribution(),
new OpenLayers.Control.ScaleLine()],
projection: toProjection,
displayProjection: fromProjection,
numZoomLevels: 20,
fractionalZoom: true
});
mapLayer = new OpenLayers.Layer.OSM(
"Local Tiles",
"tiles/${z}/${x}/${y}.png",
{
zoomOffset: 17,
resolutions: [1.194328566741945,0.5971642833709725,0.2985821416854863] // Zoom level 17 - 19
});
map.addLayers([mapLayer, vecLayer]);
// Create a map panel
mapPanel = new GeoExt.MapPanel({
title: "Map",
region: "center",
map: map,
xtype: "gx_mappanel",
center: new OpenLayers.LonLat(lon, lat),
zoom: zoom
});
// Create a grid panel for listing nodes
gridPanel = new Ext.grid.GridPanel({
title: "Nodes",
region: "east",
store: store,
width: 275,
columns: [{
header: "Name",
width: 200,
dataIndex: "name"
}, {
header: "Status",
width: 75,
dataIndex: "status"
}],
sm: new GeoExt.grid.FeatureSelectionModel({
autoPanMapOnSelection: true
})
});
// Create the main view port
new Ext.Viewport({
layout: "border",
items: [{
region: "north",
contentEl: "title",
height: 150
}, mapPanel, gridPanel]
});
var lonLat = new OpenLayers.LonLat(lon, lat).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject());
map.setCenter(lonLat, zoom);
// Attach all the event driven stuff here...
// Create a node selection pop up control
function nodeAction(feature) {
nodePop = new GeoExt.Popup({
title: 'Node selected',
location: feature,
width: 200,
html: "",
maximizable: true,
collapsible: true
});
nodePop.on({
close: function() {
if(OpenLayers.Util.indexOf(vectorLayer.selectedFeatures, this.feature) > -1) {
selectCtrl.unselect(this.feature);
}
}
});
nodePop.show();
};
// Attach the pop to node/feature selection events
var selectCtrl = new OpenLayers.Control.SelectFeature(vecLayer);
vecLayer.events.on({
featureselected: function(e) {
nodeAction(e.feature);
}
});
// Create map selection pop up control
function mapAction(lonlat) {
mapPop = new GeoExt.Popup({
title: 'Map selected',
location: lonlat,
width: 200,
html: "You clicked on (" + lonlat.lon.toFixed(2) + ", " + lonlat.lat.toFixed(2) + ")",
maximizable: true,
collapsible: true,
map: mapPanel.map,
anchored: true
});
mapPop.doLayout();
mapPop.show();
};
var mapCtrl = new OpenLayers.Control.Click({
trigger: function(evt) {
var lonlat = mapPanel.map.getLonLatFromViewPortPx(evt.xy);
lonlat.transform(new OpenLayers.Projection("EPSG:4326"), mapPanel.map.getProjectionObject());
//.transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject());
mapAction(lonlat);
}
});
mapPanel.map.addControl(mapCtrl);
mapCtrl.activate();
});
// A control to handle user clicks on the map
OpenLayers.Control.Click = OpenLayers.Class(
OpenLayers.Control, {
defaultHandlerOptions: {
single: true,
double: false,
pixelTolerance: 0,
stopSingle: true
},
initialize: function(options) {
this.handlerOptions = OpenLayers.Util.extend(
options && options.handlerOptions || {},
this.defaultHandlerOptions
);
OpenLayers.Control.prototype.initialize.apply(
this, arguments
);
this.handler = new OpenLayers.Handler.Click(
this,
{ click: this.trigger },
this.handlerOptions
);
},
CLASS_NAME: "OpenLayers.Control.Click"
}
);
Here's the GeoJSON I'm using:
{
"type": "FeatureCollection",
"features": [
{
"geometry": {
"type": "Point",
"coordinates": [
-70.3856,
38.3458
]
},
"type": "Feature",
"properties": {
"name": "Node0",
"status": "Active",
"externalGraphic": "img/node2.png",
"graphicHeight": 75, "graphicWidth": 75
},
"id": 100
},
{
"geometry": {
"type": "Point",
"coordinates": [
-70.885810,
38.344722
]
},
"type": "Feature",
"properties": {
"name": "Node1",
"status": "Active",
"externalGraphic": "img/node2.png",
"graphicHeight": 75, "graphicWidth": 75
},
"id": 101
}
]
}
Ok, here's how I dealt with the issue:
I'm using the embedded the Jetty Web server in my back-end, but
regardless, I created a servlet to respond with GeoJSON format data.
Each Feature location lon/lat is converted between projections.
(e.g. EPSG:4326 to EPSG:900913)
The lon/lat projection conversation leveraged the GeoTools Java API.
This blog post was particularly helpful
(http://ariasprado.name/2012/08/13/quick-and-dirty-coordinate-transforming-using-geotools.html)
Note that you'll need to go through a fair bit of trial and error if
you only want to include the jars required for converting yout
projections. GeoTools is large, does a lot, and has a number of
jars.
Now when the GeoExt.data.ProtocolProxy loads my GeoJSON content it's already in OSM compatible EPSG:900913. I would have liked to deal with this entirely in GeoExt/OpenLayer, but there doesn't appear to be an easy way. I will acknowledge that GeoExt and OpenLayers don't have super great reference documentation to follow.
I'd include my GeoTools code but "Arias Prado GIS Ramblings" blog post above does a better job than I could. Again though, note that you'll have to trial and error the jars. Projection encoders are loaded dynamically, and they in turn have class dependencies from other jars.

How to extend L.Control.Layers to add custrom filters and is it a good way to do it?

I have markers on a map which represent workers/group of workers and tasks/group of tasks.
Each workers has a list of skills and each tasks require one skill.
I'd like to be able to toggle the display of all the workers or tasks (easily done with L.featureGroup() and L.control.layers()) but I would also like to filter the current display on skills.
What I mean by "current display" is that if I've decided to hide workers and only display tasks, when I toggle the display for one skill, I don't want the workers having that skill to show up.
Which means that I can't use regular featureGroup and regular controlLayer to do so since it has no way to perform filtering/applying conditions on show/hide. It either show every markers in the group or hide them all.
I'm tempted to fork the Control.Layers and add some callback based logic to it, but I wonder if there is better way of doing it ?
Edit:
// Backbone stuff
var WorkerModel = Backbone.Model.extend({});
var TaskModel = Backbone.Model.extend({});
var WorkersCollection = Backbone.Collection.extend({
model: WorkerModel
});
var TasksCollection = Backbone.Collection.extend({
model: TaskModel
});
var workersData = [{
id: 1,
name: 'Bob',
marker: L.marker(new L.LatLng(45,3)),
coordinates: {
lat: 45,
lng: 3
},
skills: {
skillA: {
name: 'Foo',
level: 1
},
skillB: {
name: 'Bar',
level: 7
},
}
}, {
id: 2,
name: 'John',
marker: L.marker(new L.LatLng(48,2)),
coordinates: {
lat: 48,
lng: 2
},
skills: {
skillA: {
name: 'Foo',
level: 4
},
skillD : {
name: 'Zop',
level: 3
}
}
}];
var tasksData = [{
id: 1,
requiredSkill: 'skillA',
requiredLevel: 5,
duration: '5',
marker: L.marker(new L.LatLng(50,2)),
coordinates: {
lat: 50,
lng: 2
}
}, {
id: 2,
requiredSkill: 'skillB',
requiredLeve: 2,
duration: '1',
marker: L.marker(new L.LatLng(47,3)),
coordinates: {
lat: 47,
lng: 3
}
}];
var workers = new WorkersCollection(workersData);
var tasks = new TasksCollection(tasksData);
// We get all the markers that relates to a workers or a tasks with the skillA
var workersMarkerWithSkillA = workers.chain().filter(worker) {
return worker.get('skills').has('skillA');
}).map(function(worker) {
return worker.get('marker');
}).value();
var tasksMarkerWithSkillA = tasks.chain().filter(worker) {
return worker.get('skills').has('skillA');
}).map(function(worker) {
return worker.get('marker');
}).value();
var markersWithSkillA = _.concat(workersMarkerWithSkillA, tasksMarkerWithSkillA);
// Same thing with skillB
var workersMarkerWithSkillB = workers.chain().filter(worker) {
return worker.get('skills').has('skillB');
}).map(function(worker) {
return worker.get('marker');
}).value();
var tasksMarkerWithSkillB = tasks.chain().filter(worker) {
return worker.get('skills').has('skillB');
}).map(function(worker) {
return worker.get('marker');
}).value();
var markersWithSkillB = _.concat(workersMarkerWithSkillB, tasksMarkerWithSkillB);
// Make the map
var map = L.map('map');
var tileLayer = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: 'Map data © OpenStreetMap contributors' });
map.addLayer(tileLayer);
// Make the layers
var workersLayer = new L.featureGroup(workers.pluck('marker'));
var tasksLayer = new L.featureGroup(taskLayer.pluck('marker'));
var skillALayer = new L.featureGroup(markersWithSkillA);
var skillBLayer = new L.featureGroup(markersWithSkillB);
// Then we add everything on the map
var controlLayer = L.control.layers(null, [workersLayer, tasksLayer, markersWithSkillA, markersWithSkillB]);
controlLayer.addTo(map);
I ended up forking the Control.Layers as planned, but did very little change to it. I simply added a placeholder section in the HTML it generates.
Then, I build a FilterModule in my Backbone Marionette App. The region for that module would be the placeholder I previously defined, and the filtering mechanism is done by a function in the module, which is great since it really depends on the data I show, and not really on the map used to display it.
It's not entirely satisfying since it means hard coupling the App to a forked version of some Leaflet parts, but the change in the fork being minimal it's not that big of a deal, plus, on the Backbone side, it really is only a DOM#id to change and the view will show up somewhere else.
You may wonder why I did not simply add a new element in the view instead of filling the original Control.Layers's HTML which would have prevented the coupling I described. It's simply because I may have from 0 to 20 filters based on the data shown and the positioning issue it creates was not worth it. The goal was also to have these filters near the native Layer toggle.

extjs 2.3 grid fails on store update

First time posting, have only started using extjs 2.3 (at my work) and have run into a strange issue. Basically I have an option for a user to get SLD (straight line distance) between a location they have selected and a number of predefined locations, so the users clicks the SLD button, a new window opens which does the following, loads predefined locations into a jsonstore, links this store into a grid in the new window, when the store is created I also send a request to googles directions service to return the driving distance between the locations, on callback I add this data to store which in turns updates the grid.
The issue I see is, the first time the SLD button is clicked, the grid displays the information, and then the google callback adds the extra data into the store and I can see this displayed on the grid. I have a back button on the window, which when clicked returns the user to the menu window, destroys the SLD window and empties the store, so there is no trace of the SLD window any more. The issue will happen now when I click the SLD button again on the main menu, I can see the grid with data but now when the google callback returns and updates the store I see that the cells look like they have been edited and not saved.
On my production machine this issue does not happen when I use Firefox or Chrome, only happens on IE, however I have wrote a small jsFiddle to reproduce the issue and now get the issue to happen on Chrome when I run the test.
I can't understand how it can work correctly the first time, then the second time have this issue, and basically its running the same code as the first time!
This is what my test looks like, have added dummy data and simplified things to reproduce issue
var testData = [
{'name': 'home', 'distance': 16.5, 'driving_distance': 0 },
{'name': 'work', 'distance': 35.2, 'driving_distance': 0 },
{'name': 'gym', 'distance': 12.8, 'driving_distance': 0 },
];
var locations;
// create store and load it with data
function createStore() {
locations = new Ext.data.JsonStore({
data: testData,
sortInfo: {
field: 'distance',
direction: 'ASC'
},
fields: [
{ name: 'name' },
{ name: 'distance', type: 'float' },
{ name: 'driving_distance', type: 'float' }
]
});
var myLocation = new google.maps.LatLng( '55.033778', '-7.125324' );
var anyLocation = new google.maps.LatLng( '54.972441', '-7.345526' );
var directionsService = new google.maps.DirectionsService();
var request = {
origin: new google.maps.LatLng( '55.033778', '-7.125324' ),
destination: anyLocation,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
// get driving distance from myLocation to anyLocation and update locations store
for ( var x = 0; x < locations.data.length; x++ )
{
// call directions service
directionsService.route(request, function(response, status) {
// do stuff if we get a result
if (status == google.maps.DirectionsStatus.OK) {
// update store items to use same value just for text purposes
var distance = response.routes[0].legs[0].distance.value;
distance = distance / 1000;
// update on return call even though it updating the same thing 3 times
locations.data.items[0].set('driving_distance', distance.toFixed(1));
locations.data.items[1].set('driving_distance', (distance + 10.1).toFixed(1) );
locations.data.items[2].set('driving_distance', (distance + 23.3).toFixed(1) );
locations.commitChanges();
}
});
}
}
new Ext.Window ({
// menu normally consists of a combo box in which a user can select SLD
title: 'Menu - cut down',
id: 'rightClickWindow',
headerPosition: 'left',
scope: this,
buttons: [{
text: 'SLD',
id: 'SLDButton',
handler: function () {
// hide menu window
Ext.getCmp('rightClickWindow').hide();
// create store
createStore();
// create SLD window
new Ext.Window ({
title: 'SLD',
id: 'createSLDWindow',
headerPosition: 'left',
width: 450,
scope: this,
items: [{
xtype: 'grid',
id: 'SLDGrid',
singleSelect: true,
store: locations,
columns: [
{id: 'name', header: 'Location', width: 160, sortable: false, dataIndex: 'name'},
{header: 'SLD', width: 80, align: 'center', sortable: false, renderer: 'distance', dataIndex: 'distance'},
{header: 'Driving Distance', width: 90, align: 'center', sortable: false, renderer: 'driving_distance', dataIndex: 'driving_distance'}],
stripeRows: true,
autoExpandColumn: 'name',
enableHdMenu: false,
height: 250,
header: false
} ],
buttons: [{
text: 'Back',
id: 'SLDBackButton',
handler: function () {
// destroy SLD window
Ext.getCmp('createSLDWindow').destroy();
// show menu window
Ext.getCmp('rightClickWindow').show();
// destroy store
locations.loadData([],false);
}
}],
listeners: {
close: function (form) {
// destory everything
Ext.getCmp('createSLDWindow').destroy();
Ext.getCmp('rightClickWindow').destroy();
// destroy store
locations.loadData([],false);
}
}
}).show();
}
}]
}).show();
jsFiddle http://jsfiddle.net/UDkDY/74/
to reproduce click SLD -> back -> SLD
I think there is a problem with the way you update the values :
you send 3 requests (one for each line in the grid), but on the callback of each request you update ALL the lines (when you should only update the line corresponding to the request).
Example :
http://jsfiddle.net/xer0d0he/
-

Categories

Resources