Commit 23d671cf authored by Rodrigo Tapia-McClung's avatar Rodrigo Tapia-McClung

Testing first flowmaps

parent f7d924f2
{
"type": "FeatureCollection",
"name": "desde",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "id_orig": 11007, "muni_origen": "Celaya", "xo": -100.78786496799999, "yo": 20.537565599299999, "ocupados": "Terciario", "viajes": 578, "id_dest": 11017, "muni_destino": null, "xd": -101.368320763, "yd": 20.699813496699999, "personas": 578 }, "geometry": { "type": "Point", "coordinates": [ -100.78786496799999, 20.537565599299999 ] } },
{ "type": "Feature", "properties": { "id_orig": 11007, "muni_origen": "Celaya", "xo": -100.78786496799999, "yo": 20.537565599299999, "ocupados": "Terciario", "viajes": 623, "id_dest": 11027, "muni_destino": null, "xd": -101.165096104, "yd": 20.625110122500001, "personas": 623 }, "geometry": { "type": "Point", "coordinates": [ -100.78786496799999, 20.537565599299999 ] } },
{ "type": "Feature", "properties": { "id_orig": 11020, "muni_origen": "León", "xo": -101.622402619, "yo": 21.124749248400001, "ocupados": "Primario", "viajes": 643, "id_dest": 11017, "muni_destino": null, "xd": -101.368320763, "yd": 20.699813496699999, "personas": 643 }, "geometry": { "type": "Point", "coordinates": [ -101.622402619, 21.124749248400001 ] } },
{ "type": "Feature", "properties": { "id_orig": 11007, "muni_origen": "Celaya", "xo": -100.78786496799999, "yo": 20.537565599299999, "ocupados": "Terciario", "viajes": 660, "id_dest": 11011, "muni_destino": "Cortazar", "xd": -100.938370267, "yd": 20.429113635299998, "personas": 660 }, "geometry": { "type": "Point", "coordinates": [ -100.78786496799999, 20.537565599299999 ] } },
{ "type": "Feature", "properties": { "id_orig": 22014, "muni_origen": "Querétaro", "xo": -100.452710294, "yo": 20.7288053752, "ocupados": "Primario", "viajes": 695, "id_dest": 11015, "muni_destino": "Guanajuato", "xd": -101.23732891900001, "yd": 21.018454309199999, "personas": 695 }, "geometry": { "type": "Point", "coordinates": [ -100.452710294, 20.7288053752 ] } },
{ "type": "Feature", "properties": { "id_orig": 11017, "muni_origen": "Irapuato", "xo": -101.368320763, "yo": 20.699813496699999, "ocupados": "Terciario", "viajes": 729, "id_dest": 11020, "muni_destino": null, "xd": -101.622402619, "yd": 21.124749248400001, "personas": 729 }, "geometry": { "type": "Point", "coordinates": [ -101.368320763, 20.699813496699999 ] } },
{ "type": "Feature", "properties": { "id_orig": 11007, "muni_origen": "Celaya", "xo": -100.78786496799999, "yo": 20.537565599299999, "ocupados": "Terciario", "viajes": 731, "id_dest": 11044, "muni_destino": "Villagrán", "xd": -100.992512401, "yd": 20.541961899299999, "personas": 731 }, "geometry": { "type": "Point", "coordinates": [ -100.78786496799999, 20.537565599299999 ] } },
{ "type": "Feature", "properties": { "id_orig": 22014, "muni_origen": "Querétaro", "xo": -100.452710294, "yo": 20.7288053752, "ocupados": "Primario", "viajes": 802, "id_dest": 22016, "muni_destino": "San Juan del Rí­o", "xd": -100.00038001, "yd": 20.368911535500001, "personas": 802 }, "geometry": { "type": "Point", "coordinates": [ -100.452710294, 20.7288053752 ] } },
{ "type": "Feature", "properties": { "id_orig": 11017, "muni_origen": "Irapuato", "xo": -101.368320763, "yo": 20.699813496699999, "ocupados": "Terciario", "viajes": 807, "id_dest": 11015, "muni_destino": null, "xd": -101.23732891900001, "yd": 21.018454309199999, "personas": 807 }, "geometry": { "type": "Point", "coordinates": [ -101.368320763, 20.699813496699999 ] } },
{ "type": "Feature", "properties": { "id_orig": 11020, "muni_origen": "León", "xo": -101.622402619, "yo": 21.124749248400001, "ocupados": "Primario", "viajes": 1286, "id_dest": 11015, "muni_destino": null, "xd": -101.23732891900001, "yd": 21.018454309199999, "personas": 1286 }, "geometry": { "type": "Point", "coordinates": [ -101.622402619, 21.124749248400001 ] } },
{ "type": "Feature", "properties": { "id_orig": 11007, "muni_origen": "Celaya", "xo": -100.78786496799999, "yo": 20.537565599299999, "ocupados": "Terciario", "viajes": 1301, "id_dest": 22014, "muni_destino": null, "xd": -100.452710294, "yd": 20.7288053752, "personas": 1301 }, "geometry": { "type": "Point", "coordinates": [ -100.78786496799999, 20.537565599299999 ] } },
{ "type": "Feature", "properties": { "id_orig": 22014, "muni_origen": "Querétaro", "xo": -100.452710294, "yo": 20.7288053752, "ocupados": "Primario", "viajes": 2247, "id_dest": 22006, "muni_destino": "Corregidora", "xd": -100.441074888, "yd": 20.485442072800002, "personas": 2247 }, "geometry": { "type": "Point", "coordinates": [ -100.452710294, 20.7288053752 ] } },
{ "type": "Feature", "properties": { "id_orig": 11020, "muni_origen": "León", "xo": -101.622402619, "yo": 21.124749248400001, "ocupados": "Primario", "viajes": 2635, "id_dest": 11031, "muni_destino": "San Francisco del Rincón", "xd": -101.78851964499999, "yd": 20.919124081100001, "personas": 2635 }, "geometry": { "type": "Point", "coordinates": [ -101.622402619, 21.124749248400001 ] } },
{ "type": "Feature", "properties": { "id_orig": 11017, "muni_origen": "Irapuato", "xo": -101.368320763, "yo": 20.699813496699999, "ocupados": "Terciario", "viajes": 3079, "id_dest": 11027, "muni_destino": "Salamanca", "xd": -101.165096104, "yd": 20.625110122500001, "personas": 3079 }, "geometry": { "type": "Point", "coordinates": [ -101.368320763, 20.699813496699999 ] } },
{ "type": "Feature", "properties": { "id_orig": 24028, "muni_origen": "San Luis Potosí", "xo": -100.96088844000001, "yo": 22.270479009100001, "ocupados": "Secundario", "viajes": 3179, "id_dest": 24035, "muni_destino": "Soledad de Graciano Sánchez", "xd": -100.85513169799999, "yd": 22.2716259147, "personas": 3179 }, "geometry": { "type": "Point", "coordinates": [ -100.96088844, 22.270479009100001 ] } },
{ "type": "Feature", "properties": { "id_orig": 1001, "muni_origen": "Aguascalientes", "xo": -102.29580290200001, "yo": 21.811436231199998, "ocupados": "Secundario", "viajes": 3201, "id_dest": 1011, "muni_destino": "San Francisco de los Romo", "xd": -102.22902138400001, "yd": 22.03307051, "personas": 3201 }, "geometry": { "type": "Point", "coordinates": [ -102.295802902, 21.811436231199998 ] } },
{ "type": "Feature", "properties": { "id_orig": 1001, "muni_origen": "Aguascalientes", "xo": -102.29580290200001, "yo": 21.811436231199998, "ocupados": "Secundario", "viajes": 3322, "id_dest": 1005, "muni_destino": "Jesús María", "xd": -102.44564926, "yd": 21.932117505499999, "personas": 3322 }, "geometry": { "type": "Point", "coordinates": [ -102.295802902, 21.811436231199998 ] } },
{ "type": "Feature", "properties": { "id_orig": 11020, "muni_origen": "León", "xo": -101.622402619, "yo": 21.124749248400001, "ocupados": "Primario", "viajes": 4180, "id_dest": 11037, "muni_destino": null, "xd": -101.43778362899999, "yd": 20.963115350199999, "personas": 4180 }, "geometry": { "type": "Point", "coordinates": [ -101.622402619, 21.124749248400001 ] } },
{ "type": "Feature", "properties": { "id_orig": 22014, "muni_origen": "Querétaro", "xo": -100.452710294, "yo": 20.7288053752, "ocupados": "Primario", "viajes": 4226, "id_dest": 22011, "muni_destino": "El Marqués", "xd": -100.278617514, "yd": 20.720228286200001, "personas": 4226 }, "geometry": { "type": "Point", "coordinates": [ -100.452710294, 20.7288053752 ] } },
{ "type": "Feature", "properties": { "id_orig": 11017, "muni_origen": "Irapuato", "xo": -101.368320763, "yo": 20.699813496699999, "ocupados": "Terciario", "viajes": 7164, "id_dest": 11037, "muni_destino": "Silao", "xd": -101.43778362899999, "yd": 20.963115350199999, "personas": 7164 }, "geometry": { "type": "Point", "coordinates": [ -101.368320763, 20.699813496699999 ] } }
]
}
...@@ -151,6 +151,10 @@ ...@@ -151,6 +151,10 @@
<script type="text/javascript" src="../js/Leaflet.Sync.js"></script> <script type="text/javascript" src="../js/Leaflet.Sync.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
<script type="text/javascript" src="../js/jszip.min.js"></script> <script type="text/javascript" src="../js/jszip.min.js"></script>
<!-- load animation tweening lib requirement for CanvasFlowMapLayer -->
<script src="https://unpkg.com/@tweenjs/tween.js@18.5/dist/tween.umd.js"></script>
<script src="../js/CanvasFlowmapLayer.js"></script>
<script src="../js/centropais_functions.js"></script> <script src="../js/centropais_functions.js"></script>
<script src="../js/centropais_basemap.js"></script> <script src="../js/centropais_basemap.js"></script>
<script src="../js/grijalva_charts.js"></script> <script src="../js/grijalva_charts.js"></script>
......
(function(factory, window) {
// module loaders support for LeafletJS plugins, see:
// https://github.com/Leaflet/Leaflet/blob/master/PLUGIN-GUIDE.md#module-loaders
// AMD module that relies on "leaflet"
if (typeof define === 'function' && define.amd) {
define(['leaflet'], factory);
// Common JS module that relies on "leaflet"
} else if (typeof exports === 'object') {
module.exports = factory(require('leaflet'));
}
// attach plugin to the global leaflet "L" variable
if (typeof window !== 'undefined' && window.L) {
window.L.CanvasFlowmapLayer = factory(L);
window.L.canvasFlowmapLayer = function(originAndDestinationGeoJsonPoints, opts) {
return new window.L.CanvasFlowmapLayer(originAndDestinationGeoJsonPoints, opts);
};
}
}(function(L) {
// layer source code
var canvasRenderer = L.canvas();
var CanvasFlowmapLayer = L.GeoJSON.extend({
options: {
// this is only a default option example,
// developers will most likely need to provide this
// options object with values unique to their data
originAndDestinationFieldIds: {
originUniqueIdField: 'origin_id',
originGeometry: {
x: 'origin_lon',
y: 'origin_lat'
},
destinationUniqueIdField: 'destination_id',
destinationGeometry: {
x: 'destination_lon',
y: 'destination_lat'
}
},
canvasBezierStyle: {
type: 'simple',
symbol: {
// use canvas styling options (compare to CircleMarker styling below)
strokeStyle: 'rgba(255, 0, 51, 0.8)',
lineWidth: 0.75,
lineCap: 'round',
shadowColor: 'rgb(255, 0, 51)',
shadowBlur: 1.5
}
},
animatedCanvasBezierStyle: {
type: 'simple',
symbol: {
// use canvas styling options (compare to CircleMarker styling below)
strokeStyle: 'rgb(255, 46, 88)',
lineWidth: 1.25,
lineDashOffsetSize: 4, // custom property used with animation sprite sizes
lineCap: 'round',
shadowColor: 'rgb(255, 0, 51)',
shadowBlur: 2
}
},
// valid values: 'selection' or 'all'
// use 'all' to display all Bezier paths immediately
// use 'selection' if Bezier paths will be drawn with user interactions
pathDisplayMode: 'all',
wrapAroundCanvas: true,
animationStarted: false,
animationEasingFamily: 'Cubic',
animationEasingType: 'In',
animationDuration: 2000,
pointToLayer: function(geoJsonPoint, latlng) {
return L.circleMarker(latlng);
},
style: function(geoJsonFeature) {
// use leaflet's path styling options
// since the GeoJSON feature properties are modified by the layer,
// developers can rely on the "isOrigin" property to set different
// symbols for origin vs destination CircleMarker stylings
if (geoJsonFeature.properties.isOrigin) {
return {
renderer: canvasRenderer, // recommended to use L.canvas()
radius: 5,
weight: 1,
color: 'rgb(195, 255, 62)',
fillColor: 'rgba(195, 255, 62, 0.6)',
fillOpacity: 0.6
};
} else {
return {
renderer: canvasRenderer,
radius: 2.5,
weight: 0.25,
color: 'rgb(17, 142, 170)',
fillColor: 'rgb(17, 142, 170)',
fillOpacity: 0.7
};
}
}
},
_customCanvases: [],
initialize: function(geoJson, options) {
// same as L.GeoJSON intialize method, but first performs custom GeoJSON
// data parsing and reformatting before finally calling L.GeoJSON addData method
L.setOptions(this, options);
this._animationPropertiesStatic = {
offset: 0,
resetOffset: 200,
repeat: Infinity,
yoyo: false
};
this._animationPropertiesDynamic = {
duration: null,
easingInfo: null
};
this._layers = {};
// beginning of customized initialize method
if (geoJson && this.options.originAndDestinationFieldIds) {
this.setOriginAndDestinationGeoJsonPoints(geoJson);
}
// establish animation properties using Tween.js library
// currently requires the developer to add it to their own app index.html
// TODO: find better way to wrap it up in this layer source code
if (window.hasOwnProperty('TWEEN')) {
// set this._animationPropertiesDynamic.duration value
this.setAnimationDuration(this.options.animationDuration);
// set this._animationPropertiesDynamic.easingInfo value
this.setAnimationEasing(this.options.animationEasingFamily, this.options.animationEasingType);
// initiate the active animation tween
this._animationTween = new TWEEN.Tween(this._animationPropertiesStatic)
.to({
offset: this._animationPropertiesStatic.resetOffset
}, this._animationPropertiesDynamic.duration)
.easing(this._animationPropertiesDynamic.easingInfo.tweenEasingFunction)
.repeat(this._animationPropertiesStatic.repeat)
.yoyo(this._animationPropertiesStatic.yoyo)
.start();
} else {
// Tween.js lib isn't available,
// ensure that animations aren't attempted at the beginning
this.options.animationStarted = false;
}
},
setOriginAndDestinationGeoJsonPoints: function(geoJsonFeatureCollection) {
if (geoJsonFeatureCollection.features) {
var configOriginGeometryObject = this.options.originAndDestinationFieldIds.originGeometry;
var configDestinationGeometryObject = this.options.originAndDestinationFieldIds.destinationGeometry;
geoJsonFeatureCollection.features.forEach(function(feature, index) {
if (feature.type === 'Feature' && feature.geometry && feature.geometry.type === 'Point') {
// origin feature -- modify attribute properties and geometry
feature.properties.isOrigin = true;
feature.properties._isSelectedForPathDisplay = this.options.pathDisplayMode === 'all' ? true : false;
feature.properties._uniqueId = index + '_origin';
feature.geometry.coordinates = [
feature.properties[configOriginGeometryObject.x],
feature.properties[configOriginGeometryObject.y]
];
// destination feature -- clone, modify, and push to feature collection
var destinationFeature = JSON.parse(JSON.stringify(feature));
destinationFeature.properties.isOrigin = false;
destinationFeature.properties._isSelectedForPathDisplay = false;
destinationFeature.properties._uniqueId = index + '_destination';
destinationFeature.geometry.coordinates = [
destinationFeature.properties[configDestinationGeometryObject.x],
destinationFeature.properties[configDestinationGeometryObject.y]
];
geoJsonFeatureCollection.features.push(destinationFeature);
}
}, this);
// all origin/destination features are available for future internal used
// but only a filtered subset of these are drawn on the map
this.originAndDestinationGeoJsonPoints = geoJsonFeatureCollection;
var geoJsonPointsToDraw = this._filterGeoJsonPointsToDraw(geoJsonFeatureCollection);
this.addData(geoJsonPointsToDraw);
} else {
// TODO: improved handling of invalid incoming GeoJson FeatureCollection?
this.originAndDestinationGeoJsonPoints = null;
}
return this;
},
onAdd: function(map) {
// call the L.GeoJSON onAdd method,
// then continue with custom code
L.GeoJSON.prototype.onAdd.call(this, map);
// create new canvas element for optional, animated bezier curves
this._animationCanvasElement = this._insertCustomCanvasElement(map, this.options);
// create new canvas element for manually drawing bezier curves
// - most of the magic happens in this canvas element
// - this canvas element is established last because it will be
// inserted before (underneath) the animation canvas element
this._canvasElement = this._insertCustomCanvasElement(map, this.options);
// create a reference to both canvas elements in an array for convenience
this._customCanvases = [this._canvasElement, this._animationCanvasElement]
// establish custom event listeners
this.on('click mouseover', this._modifyInteractionEvent, this);
map.on('move', this._resetCanvas, this);
map.on('moveend', this._resetCanvasAndWrapGeoJsonCircleMarkers, this);
map.on('resize', this._resizeCanvas, this);
if (map.options.zoomAnimation && L.Browser.any3d) {
map.on('zoomanim', this._animateZoom, this);
}
// calculate initial size and position of canvas
// and draw its content for the first time
this._resizeCanvas();
this._resetCanvasAndWrapGeoJsonCircleMarkers();
return this;
},
onRemove: function(map) {
// call the L.GeoJSON onRemove method,
// then continue with custom code
L.GeoJSON.prototype.onRemove.call(this, map);
this._clearCanvas();
this._customCanvases.forEach(function(canvas) {
L.DomUtil.remove(canvas);
});
// remove custom event listeners
this.off('click mouseover', this._modifyInteractionEvent, this);
map.off('move', this._resetCanvas, this);
map.off('moveend', this._resetCanvasAndWrapGeoJsonCircleMarkers, this);
map.off('resize', this._resizeCanvas, this);
if (map.options.zoomAnimation) {
map.off('zoomanim', this._animateZoom, this);
}
return this;
},
bringToBack: function() {
// call the L.GeoJSON bringToBack method to manage the point graphics
L.GeoJSON.prototype.bringToBack.call(this);
// keep the animation canvas element on top of the main canvas element
L.DomUtil.toBack(this._animationCanvasElement);
// keep the main canvas element underneath the animation canvas element
L.DomUtil.toBack(this._canvasElement);
return this;
},
bringToFront: function() {
// keep the main canvas element underneath the animation canvas element
L.DomUtil.toFront(this._canvasElement);
// keep the animation canvas element on top of the main canvas element
L.DomUtil.toFront(this._animationCanvasElement);
// call the L.GeoJSON bringToFront method to manage the point graphics
L.GeoJSON.prototype.bringToFront.call(this);
return this;
},
setAnimationDuration: function(milliseconds) {
milliseconds = Number(milliseconds) || this.options.animationDuration;
// change the tween duration on the active animation tween
if (this._animationTween) {
this._animationTween.to({
offset: this._animationPropertiesStatic.resetOffset
}, milliseconds);
}
this._animationPropertiesDynamic.duration = milliseconds;
},
setAnimationEasing: function(easingFamily, easingType) {
var tweenEasingFunction;
if (
TWEEN.Easing.hasOwnProperty(easingFamily) &&
TWEEN.Easing[easingFamily].hasOwnProperty(easingType)
) {
tweenEasingFunction = TWEEN.Easing[easingFamily][easingType];
} else {
easingFamily = this.options.animationEasingFamily;
easingType = this.options.animationEasingType;
tweenEasingFunction = TWEEN.Easing[easingFamily][easingType];
}
// change the tween easing function on the active animation tween
if (this._animationTween) {
this._animationTween.easing(tweenEasingFunction);
}
this._animationPropertiesDynamic.easingInfo = {
easingFamily: easingFamily,
easingType: easingType,
tweenEasingFunction: tweenEasingFunction
};
},
getAnimationEasingOptions: function(prettyPrint) {
var tweenEasingConsoleOptions = {};
var tweenEasingOptions = {};
Object.keys(TWEEN.Easing).forEach(function(family) {
tweenEasingConsoleOptions[family] = {
types: Object.keys(TWEEN.Easing[family]).join('", "')
};
tweenEasingOptions[family] = {
types: Object.keys(TWEEN.Easing[family])
};
});
if (!!prettyPrint) {
console.table(tweenEasingConsoleOptions);
}
return tweenEasingOptions;
},
playAnimation: function() {
this.options.animationStarted = true;
this._redrawCanvas();
},
stopAnimation: function() {
this.options.animationStarted = false;
this._redrawCanvas();
},
selectFeaturesForPathDisplay: function(selectionFeatures, selectionMode) {
this._applyFeaturesSelection(selectionFeatures, selectionMode, '_isSelectedForPathDisplay');
},
selectFeaturesForPathDisplayById: function(uniqueOriginOrDestinationIdField, idValue, originBoolean, selectionMode) {
if (
uniqueOriginOrDestinationIdField !== this.options.originAndDestinationFieldIds.originUniqueIdField &&
uniqueOriginOrDestinationIdField !== this.options.originAndDestinationFieldIds.destinationUniqueIdField
) {
console.error('Invalid unique id field supplied for origin or destination. It must be one of these: ' +
this.options.originAndDestinationFieldIds.originUniqueIdField + ', ' + this.options.originAndDestinationFieldIds.destinationUniqueIdField);
return;
}
var existingOriginOrDestinationFeature = this.originAndDestinationGeoJsonPoints.features.filter(function(feature) {
return feature.properties.isOrigin === originBoolean &&
feature.properties[uniqueOriginOrDestinationIdField] === idValue;
})[0];
var odInfo = this._getSharedOriginOrDestinationFeatures(existingOriginOrDestinationFeature);
if (odInfo.isOriginFeature) {
this.selectFeaturesForPathDisplay(odInfo.sharedOriginFeatures, selectionMode);
} else {
this.selectFeaturesForPathDisplay(odInfo.sharedDestinationFeatures, selectionMode);
}
},
clearAllPathSelections: function() {
this.originAndDestinationGeoJsonPoints.features.forEach(function(feature) {
feature.properties._isSelectedForPathDisplay = false;
});
this._resetCanvas();
},
_filterGeoJsonPointsToDraw: function(geoJsonFeatureCollection) {
var newGeoJson = {
type: 'FeatureCollection',
features: []
};
var originUniqueIdValues = [];
var destinationUniqueIdValues = [];
var originUniqueIdField = this.options.originAndDestinationFieldIds.originUniqueIdField;
var destinationUniqueIdField = this.options.originAndDestinationFieldIds.destinationUniqueIdField;
geoJsonFeatureCollection.features.forEach(function(feature) {
var isOrigin = feature.properties.isOrigin;
if (isOrigin && originUniqueIdValues.indexOf(feature.properties[originUniqueIdField]) === -1) {
originUniqueIdValues.push(feature.properties[originUniqueIdField]);
newGeoJson.features.push(feature);
} else if (!isOrigin && destinationUniqueIdValues.indexOf(feature.properties[destinationUniqueIdField]) === -1) {
destinationUniqueIdValues.push(feature.properties[destinationUniqueIdField]);
newGeoJson.features.push(feature);
} else {
// do not attempt to draw an origin or destination circle on the canvas if it is already in one of the tracking arrays
return;
}
});
return newGeoJson;
},
_insertCustomCanvasElement: function(map, options) {
var canvas = L.DomUtil.create('canvas', 'leaflet-zoom-animated');
var originProp = L.DomUtil.testProp(['transformOrigin', 'WebkitTransformOrigin', 'msTransformOrigin']);
canvas.style[originProp] = '50% 50%';
var pane = map.getPane(options.pane);
pane.insertBefore(canvas, pane.firstChild);
return canvas;
},
_modifyInteractionEvent: function(e) {
var odInfo = this._getSharedOriginOrDestinationFeatures(e.layer.feature);
e.isOriginFeature = odInfo.isOriginFeature;
e.sharedOriginFeatures = odInfo.sharedOriginFeatures;
e.sharedDestinationFeatures = odInfo.sharedDestinationFeatures;
},
_getSharedOriginOrDestinationFeatures: function(testFeature) {
var isOriginFeature = testFeature.properties.isOrigin;
var sharedOriginFeatures = [];
var sharedDestinationFeatures = [];
if (isOriginFeature) {
// for an ORIGIN point that was interacted with,
// make an array of all other ORIGIN features with the same ORIGIN ID field
var originUniqueIdField = this.options.originAndDestinationFieldIds.originUniqueIdField;
var testFeatureOriginId = testFeature.properties[originUniqueIdField];
sharedOriginFeatures = this.originAndDestinationGeoJsonPoints.features.filter(function(feature) {
return feature.properties.isOrigin &&
feature.properties[originUniqueIdField] === testFeatureOriginId;
});
} else {
// for a DESTINATION point that was interacted with,
// make an array of all other ORIGIN features with the same DESTINATION ID field
var destinationUniqueIdField = this.options.originAndDestinationFieldIds.destinationUniqueIdField;
var testFeatureDestinationId = testFeature.properties[destinationUniqueIdField];
sharedDestinationFeatures = this.originAndDestinationGeoJsonPoints.features.filter(function(feature) {
return feature.properties.isOrigin &&
feature.properties[destinationUniqueIdField] === testFeatureDestinationId;
});
}
return {
isOriginFeature: isOriginFeature, // Boolean
sharedOriginFeatures: sharedOriginFeatures, // Array of features
sharedDestinationFeatures: sharedDestinationFeatures // Array of features
};
},
_applyFeaturesSelection: function(selectionFeatures, selectionMode, selectionAttributeName) {
var selectionIds = selectionFeatures.map(function(feature) {
return feature.properties._uniqueId;
});
if (selectionMode === 'SELECTION_NEW') {
this.originAndDestinationGeoJsonPoints.features.forEach(function(feature) {
if (selectionIds.indexOf(feature.properties._uniqueId) > -1) {
feature.properties[selectionAttributeName] = true;
} else {
feature.properties[selectionAttributeName] = false;
}
});
} else if (selectionMode === 'SELECTION_ADD') {
this.originAndDestinationGeoJsonPoints.features.forEach(function(feature) {
if (selectionIds.indexOf(feature.properties._uniqueId) > -1) {
feature.properties[selectionAttributeName] = true;
}
});
} else if (selectionMode === 'SELECTION_SUBTRACT') {
this.originAndDestinationGeoJsonPoints.features.forEach(function(feature) {
if (selectionIds.indexOf(feature.properties._uniqueId) > -1) {
feature.properties[selectionAttributeName] = false;
}
});
} else {
return;
}
this._resetCanvas();
},
_animateZoom: function(e) {
// see: https://github.com/Leaflet/Leaflet.heat
var scale = this._map.getZoomScale(e.zoom);
var offset = this._map._getCenterOffset(e.center)._multiplyBy(-scale).subtract(this._map._getMapPanePos());
if (L.DomUtil.setTransform) {
this._customCanvases.forEach(function(canvas) {
L.DomUtil.setTransform(canvas, offset, scale);
});
} else {
this._customCanvases.forEach(function(canvas) {
canvas.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ')';
});
}
},
_resizeCanvas: function() {
// update the canvas size
var size = this._map.getSize();
this._customCanvases.forEach(function(canvas) {
canvas.width = size.x;
canvas.height = size.y;
});
this._resetCanvas();
},
_resetCanvas: function() {
// update the canvas position and redraw its content
var topLeft = this._map.containerPointToLayerPoint([0, 0]);
this._customCanvases.forEach(function(canvas) {
L.DomUtil.setPosition(canvas, topLeft);
});
this._redrawCanvas();
},
_resetCanvasAndWrapGeoJsonCircleMarkers: function() {
this._resetCanvas();
// Leaflet will redraw a CircleMarker when its latLng is changed
// sometimes they are drawn 2+ times if this occurs during many "move" events
// so for now, only chang CircleMarker latlng after a single "moveend" event
this._wrapGeoJsonCircleMarkers();
},
_redrawCanvas: function() {
// draw canvas content (only the Bezier curves)
if (this._map && this.originAndDestinationGeoJsonPoints) {
this._clearCanvas();
// loop over each of the "selected" features and re-draw the canvas paths
this._drawSelectedCanvasPaths(false);
if (this._animationFrameId) {
L.Util.cancelAnimFrame(this._animationFrameId);
}
if (
this.options.animationStarted &&
this.originAndDestinationGeoJsonPoints.features.some(function(feature) {
return feature.properties._isSelectedForPathDisplay;
})
) {
// start animation loop if the layer is currently set for showing animations,
// and if there is at least 1 feature selected for displaying paths
this._animator();
}
}
},
_clearCanvas: function() {
this._customCanvases.forEach(function(canvas) {
canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
});
if (this._animationFrameId) {
L.Util.cancelAnimFrame(this._animationFrameId);
}
},
_drawSelectedCanvasPaths: function(animate) {
var ctx = animate ?
this._animationCanvasElement.getContext('2d') :
this._canvasElement.getContext('2d');
var originAndDestinationFieldIds = this.options.originAndDestinationFieldIds;
this.originAndDestinationGeoJsonPoints.features.forEach(function(feature) {
if (feature.properties._isSelectedForPathDisplay) {
var originXCoordinate = feature.properties[originAndDestinationFieldIds.originGeometry.x];
var originYCoordinate = feature.properties[originAndDestinationFieldIds.originGeometry.y];
var destinationXCoordinate = feature.properties[originAndDestinationFieldIds.destinationGeometry.x];
var destinationYCoordinate = feature.properties[originAndDestinationFieldIds.destinationGeometry.y];
// origin and destination points for drawing curved lines
// ensure that canvas features will be drawn beyond +/-180 longitude
var originLatLng = this._wrapAroundLatLng(L.latLng([originYCoordinate, originXCoordinate]));
var destinationLatLng = this._wrapAroundLatLng(L.latLng([destinationYCoordinate, destinationXCoordinate]));
// convert geometry to screen coordinates for canvas drawing
var screenOriginPoint = this._map.latLngToContainerPoint(originLatLng);
var screenDestinationPoint = this._map.latLngToContainerPoint(destinationLatLng);
// get the canvas symbol properties,
// and draw a curved canvas line
var symbol;
if (animate) {
symbol = this._getSymbolProperties(feature, this.options.animatedCanvasBezierStyle);
ctx.beginPath();
this._animateCanvasLineSymbol(ctx, symbol, screenOriginPoint, screenDestinationPoint);
ctx.stroke();
ctx.closePath();
} else {
symbol = this._getSymbolProperties(feature, this.options.canvasBezierStyle);
ctx.beginPath();
this._applyCanvasLineSymbol(ctx, symbol, screenOriginPoint, screenDestinationPoint);
ctx.stroke();
ctx.closePath();
}
}
}, this);
},
_getSymbolProperties: function(feature, canvasSymbolConfig) {
// get the canvas symbol properties
var symbol;
var filteredSymbols;
if (canvasSymbolConfig.type === 'simple') {
symbol = canvasSymbolConfig.symbol;
} else if (canvasSymbolConfig.type === 'uniqueValue') {
filteredSymbols = canvasSymbolConfig.uniqueValueInfos.filter(function(info) {
return info.value === feature.properties[canvasSymbolConfig.field];
});
symbol = filteredSymbols[0].symbol;
} else if (canvasSymbolConfig.type === 'classBreaks') {
filteredSymbols = canvasSymbolConfig.classBreakInfos.filter(function(info) {
return (
info.classMinValue <= feature.properties[canvasSymbolConfig.field] &&
info.classMaxValue >= feature.properties[canvasSymbolConfig.field]
);
});
if (filteredSymbols.length) {
symbol = filteredSymbols[0].symbol;
} else {
symbol = canvasSymbolConfig.defaultSymbol;
}
}
return symbol;
},
_applyCanvasLineSymbol: function(ctx, symbolObject, screenOriginPoint, screenDestinationPoint) {
ctx.lineCap = symbolObject.lineCap;
ctx.lineWidth = symbolObject.lineWidth;
ctx.strokeStyle = symbolObject.strokeStyle;
ctx.shadowBlur = symbolObject.shadowBlur;
ctx.shadowColor = symbolObject.shadowColor;
ctx.moveTo(screenOriginPoint.x, screenOriginPoint.y);
ctx.bezierCurveTo(screenOriginPoint.x, screenDestinationPoint.y, screenDestinationPoint.x, screenDestinationPoint.y, screenDestinationPoint.x, screenDestinationPoint.y);
},
_animateCanvasLineSymbol: function(ctx, symbolObject, screenOriginPoint, screenDestinationPoint) {
ctx.lineCap = symbolObject.lineCap;
ctx.lineWidth = symbolObject.lineWidth;
ctx.strokeStyle = symbolObject.strokeStyle;
ctx.shadowBlur = symbolObject.shadowBlur;
ctx.shadowColor = symbolObject.shadowColor;
ctx.setLineDash([symbolObject.lineDashOffsetSize, (this._animationPropertiesStatic.resetOffset - symbolObject.lineDashOffsetSize)]);
ctx.lineDashOffset = -this._animationPropertiesStatic.offset; // this makes the dot appear to move when the entire top canvas is redrawn
ctx.moveTo(screenOriginPoint.x, screenOriginPoint.y);
ctx.bezierCurveTo(screenOriginPoint.x, screenDestinationPoint.y, screenDestinationPoint.x, screenDestinationPoint.y, screenDestinationPoint.x, screenDestinationPoint.y);
},
_animator: function(time) {
this._animationCanvasElement.getContext('2d')
.clearRect(0, 0, this._animationCanvasElement.width, this._animationCanvasElement.height);
this._drawSelectedCanvasPaths(true); // draw it again to give the appearance of a moving dot with a new lineDashOffset
TWEEN.update(time);
this._animationFrameId = L.Util.requestAnimFrame(this._animator, this);
},
_wrapGeoJsonCircleMarkers: function() {
// ensure that the GeoJson point features,
// which are drawn on the map as individual CircleMarker layers,
// will be drawn beyond +/-180 longitude
this.eachLayer(function(layer) {
var wrappedLatLng = this._wrapAroundLatLng(layer.getLatLng());
layer.setLatLng(wrappedLatLng);
}, this);
},
_wrapAroundLatLng: function(latLng) {
if (this._map && this.options.wrapAroundCanvas) {
var wrappedLatLng = latLng.clone();
var mapCenterLng = this._map.getCenter().lng;
var wrapAroundDiff = mapCenterLng - wrappedLatLng.lng;
if (wrapAroundDiff < -180 || wrapAroundDiff > 180) {
wrappedLatLng.lng += (Math.round(wrapAroundDiff / 360) * 360);
}
return wrappedLatLng;
} else {
return latLng;
}
}
});
return CanvasFlowmapLayer;
}, window));
\ No newline at end of file
...@@ -955,6 +955,138 @@ const makeBaseMap = () => { ...@@ -955,6 +955,138 @@ const makeBaseMap = () => {
layerControl.addOverlay(layer_limiteMunicipal, "<img src=\"http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?version=1.3.0&service=WMS&request=GetLegendGraphic&sld_version=1.1.0&layer=c101&format=image/png&STYLE=default\" alt=\"Límite municipal\"/>"); layerControl.addOverlay(layer_limiteMunicipal, "<img src=\"http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?version=1.3.0&service=WMS&request=GetLegendGraphic&sld_version=1.1.0&layer=c101&format=image/png&STYLE=default\" alt=\"Límite municipal\"/>");
layerControl.addOverlay(layer_limiteEstatal, "Límite estatal <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src=\"http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?version=1.3.0&service=WMS&request=GetLegendGraphic&sld_version=1.1.0&layer=c100&format=image/png&STYLE=default\" alt=\"Límite Estatal\"/>"); layerControl.addOverlay(layer_limiteEstatal, "Límite estatal <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src=\"http://gaia.inegi.org.mx/NLB/tunnel/wms/wms61?version=1.3.0&service=WMS&request=GetLegendGraphic&sld_version=1.1.0&layer=c100&format=image/png&STYLE=default\" alt=\"Límite Estatal\"/>");
$.getJSON('data/viajes_ocupados_desde.json', data => {
let flowMapLayer = L.canvasFlowmapLayer(data, {
// Define origins and destination from json values
originAndDestinationFieldIds: {
originUniqueIdField: 'id_orig',
originGeometry: {
x: 'xo',
y: 'yo'
},
destinationUniqueIdField: 'id_dest',
destinationGeometry: {
x: 'xd',
y: 'yd'
}
},
// Line styles
canvasBezierStyle: {
type: 'classBreaks',
field: 'viajes',
classBreakInfos: [{
classMinValue: 0,
classMaxValue: 1000,
symbol: {
strokeStyle: '#00c5ff',
lineWidth: 0.5,
lineCap: 'round',
shadowColor: '#00c5ff',
shadowBlur: 1.5
}
}, {
classMinValue: 1001,
classMaxValue: 2000,
symbol: {
strokeStyle: '#008fdc',
lineWidth: 1.5,
lineCap: 'round',
shadowColor: '#008fdc',
shadowBlur: 1.5
}
}, {
classMinValue: 2001,
classMaxValue: 6000,
symbol: {
strokeStyle: '#005ce6',
lineWidth: 2,
lineCap: 'round',
shadowColor: '#005ce6',
shadowBlur: 1.5
}
}, {
classMinValue: 6001,
classMaxValue: 7200,
symbol: {
strokeStyle: '#4c0073',
lineWidth: 10,
lineCap: 'round',
shadowColor: '#4c0073',
shadowBlur: 1.5
}
}]
},
// Animated line styles
animatedCanvasBezierStyle: {
type: 'classBreaks',
field: 'viajes',
classBreakInfos: [{
classMinValue: 0,
classMaxValue: 1000,
symbol: {
strokeStyle: '#00c5ff',
lineWidth: 0.5,
lineDashOffsetSize: 4,
lineCap: 'round',
shadowColor: '#00c5ff',
shadowBlur: 1.5
}
}, {
classMinValue: 1001,
classMaxValue: 2000,
symbol: {
strokeStyle: '#008fdc',
lineWidth: 1.5,
lineDashOffsetSize: 4,
lineCap: 'round',
shadowColor: '#008fdc',
shadowBlur: 1.5
}
}, {
classMinValue: 2001,
classMaxValue: 6000,
symbol: {
strokeStyle: '#005ce6',
lineWidth: 4,
lineDashOffsetSize: 4,
lineCap: 'round',
shadowColor: '#005ce6',
shadowBlur: 1.5
}
}, {
classMinValue: 6001,
classMaxValue: 7200,
symbol: {
strokeStyle: '#4c0073',
lineWidth: 15,
lineDashOffsetSize: 4,
lineCap: 'round',
shadowColor: '#4c0073',
shadowBlur: 1.5
}
}]
},
// some custom options
pathDisplayMode: 'all',
animationStarted: true,
//animationEasingFamily: 'Cubic',
//animationEasingType: 'In',
animationEasingFamily: 'Linear',
animationEasingType: 'None',
animationDuration: 2000
});
flowMapLayer.addTo(map);
// Define what happens when user clicks dots
flowMapLayer.on('click', e => {
if (e.sharedOriginFeatures.length) {
flowMapLayer.selectFeaturesForPathDisplay(e.sharedOriginFeatures, 'SELECTION_NEW');
}
if (e.sharedDestinationFeatures.length) {
flowMapLayer.selectFeaturesForPathDisplay(e.sharedDestinationFeatures, 'SELECTION_NEW');
}
});
});
// Localize Leaflet.Draw texts // Localize Leaflet.Draw texts
L.drawLocal = { L.drawLocal = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment