diff --git a/cadasta/core/static/css/main.css b/cadasta/core/static/css/main.css
index 9772820a2..838cc9a72 100644
--- a/cadasta/core/static/css/main.css
+++ b/cadasta/core/static/css/main.css
@@ -6464,4 +6464,4 @@ h1 .label {
text-transform: uppercase;
padding-bottom: 2px; }
-/*# sourceMappingURL=../../../../../static/css/main.css.map */
+/*# sourceMappingURL=../../../../../static/css/main.css.map */
\ No newline at end of file
diff --git a/cadasta/core/static/css/single.scss b/cadasta/core/static/css/single.scss
index dafcf2e44..05b810382 100644
--- a/cadasta/core/static/css/single.scss
+++ b/cadasta/core/static/css/single.scss
@@ -533,7 +533,6 @@
#project-map {
width: 100%;
}
-
}
diff --git a/cadasta/core/static/js/map_utils.js b/cadasta/core/static/js/map_utils.js
index 7b920099b..ff3a4c69b 100644
--- a/cadasta/core/static/js/map_utils.js
+++ b/cadasta/core/static/js/map_utils.js
@@ -63,10 +63,9 @@ function renderFeatures(map, featuresUrl, options) {
} else {
$('#messages #loading').addClass('hidden');
if (options.fitBounds === 'locations') {
- // var bounds = markers.getBounds();
- var bounds = geoJson.getBounds();
+ var bounds = markers.getBounds();
if (bounds.isValid()) {
- map.fitBounds(bounds);
+ map.fitBounds(bounds);
}
}
}
@@ -97,7 +96,7 @@ function renderFeatures(map, featuresUrl, options) {
} else {
map.fitBounds([[-45.0, -180.0], [45.0, 180.0]]);
}
-
+
var geoJson = L.geoJson(null, {
style: { weight: 2 },
onEachFeature: function(feature, layer) {
@@ -106,17 +105,18 @@ function renderFeatures(map, featuresUrl, options) {
"
Location" +
feature.properties.type + "
" +
"");
+ "");
}
}
});
- // var markers = L.Deflate({minSize: 20, layerGroup: geoJson});
- // markers.addTo(map);
+
+ var markers = L.Deflate({minSize: 20, layerGroup: geoJson});
+ markers.addTo(map);
geoJson.addTo(map);
if (options.location) {
options.location.addTo(map);
- map.fitBounds(options.location.getBounds());
+ map.fitBounds(options.location.getBounds());
} else if (projectBounds) {
map.fitBounds(projectBounds);
}
@@ -142,7 +142,7 @@ function switch_layer_controls(map, options){
var groupedOptions = {
groupCheckboxes: false
};
- // map.removeControl(map.layerscontrol);
+ map.removeControl(map.layerscontrol);
map.layerscontrol = L.control.groupedLayers(
baseLayers, groupedOptions).addTo(map);
}
@@ -186,7 +186,3 @@ function saveOnMapEditMode() {
saveButton.dispatchEvent(clickEvent);
}
}
-
-
-
-
diff --git a/cadasta/core/static/js/smap/L.Map.Deflate4.js b/cadasta/core/static/js/smap/L.Map.Deflate4.js
new file mode 100644
index 000000000..69d8c1326
--- /dev/null
+++ b/cadasta/core/static/js/smap/L.Map.Deflate4.js
@@ -0,0 +1,141 @@
+L.Deflate4 = L.LayerGroup.extend({
+ options: {
+ minSize: 10,
+ markerCluster: false
+ },
+
+ initialize: function (options) {
+ L.Util.setOptions(this, options);
+ this._allLayers = [];
+ this._featureGroup = this.options.markerCluster ? L.markerClusterGroup() : L.featureGroup();
+ },
+
+ _isCollapsed: function(path, zoom) {
+ var bounds = path.getBounds();
+
+ var ne_px = this._map.project(bounds.getNorthEast(), zoom);
+ var sw_px = this._map.project(bounds.getSouthWest(), zoom);
+
+ var width = ne_px.x - sw_px.x;
+ var height = sw_px.y - ne_px.y;
+ return (height < this.options.minSize || width < this.options.minSize);
+ },
+
+ _getZoomThreshold: function(path) {
+ var zoomThreshold = null;
+ var zoom = this._map.getZoom();
+ if (this._isCollapsed(path, this._map.getZoom())) {
+ while (!zoomThreshold) {
+ zoom += 1;
+ if (!this._isCollapsed(path, zoom)) {
+ zoomThreshold = zoom - 1;
+ }
+ }
+ } else {
+ while (!zoomThreshold) {
+ zoom -= 1;
+ if (this._isCollapsed(path, zoom)) {
+ zoomThreshold = zoom;
+ }
+ }
+ }
+ return zoomThreshold;
+ },
+
+ addLayer: function (layer) {
+ if (layer instanceof L.FeatureGroup) {
+ for (var i in layer._layers) {
+ this.addLayer(layer._layers[i]);
+ }
+ } else {
+ var layerToAdd = layer;
+ if (layer.getBounds && !layer.zoomThreshold && !layer.marker) {
+ var zoomThreshold = this._getZoomThreshold(layer);
+ var marker = L.marker(layer.getBounds().getCenter());
+
+ if (layer._popupHandlersAdded) {
+ marker.bindPopup(layer._popup._content)
+ }
+
+ var events = layer._events;
+ for (var event in events) {
+ if (events.hasOwnProperty(event)) {
+ var listeners = events[event];
+ for (var i = 0, len = listeners.length; i < len; i++) {
+ marker.on(event, listeners[i].fn)
+ }
+ }
+ }
+
+ layer.zoomThreshold = zoomThreshold;
+ layer.marker = marker;
+ layer.zoomState = this._map.getZoom();
+
+ if (this._map.getZoom() <= zoomThreshold) {
+ layerToAdd = layer.marker;
+ }
+ this._allLayers.push(layer);
+ }
+
+ this._featureGroup.addLayer(layerToAdd);
+ }
+ },
+
+ removeLayer(layer) {
+ if (layer instanceof L.FeatureGroup) {
+ for (var i in layer._layers) {
+ this.removeLayer(layer._layers[i]);
+ }
+ } else {
+ this._featureGroup.removeLayer(layer.marker);
+ this._featureGroup.removeLayer(layer);
+
+ const index = this._allLayers.indexOf(layer);
+ if (index !== -1) { this._allLayers.splice(index, 1); }
+ }
+ },
+
+ _switchDisplay: function(layer, showMarker) {
+ if (showMarker) {
+ this._featureGroup.addLayer(layer.marker);
+ this._featureGroup.removeLayer(layer);
+ } else {
+ this._featureGroup.addLayer(layer);
+ this._featureGroup.removeLayer(layer.marker);
+ }
+ },
+
+ _deflate: function() {
+ const bounds = this._map.getBounds();
+ const endZoom = this._map.getZoom();
+ var markersToAdd = [];
+ var markersToRemove = [];
+
+ for (var i = 0, len = this._allLayers.length; i < len; i++) {
+ if (this._allLayers[i].zoomState !== endZoom && this._allLayers[i].getBounds().intersects(bounds)) {
+ this._switchDisplay(this._allLayers[i], endZoom <= this._allLayers[i].zoomThreshold);
+ this._allLayers[i].zoomState = endZoom;
+ }
+ }
+ },
+
+ getBounds: function() {
+ return this._featureGroup.getBounds();
+ },
+
+ onAdd: function(map) {
+ this._featureGroup.addTo(map);
+ this._map.on('zoomend', this._deflate, this);
+ this._map.on('dragend', this._deflate, this);
+ },
+
+ onRemove: function(map) {
+ map.removeLayer(this._featureGroup);
+ this._map.off('zoomend', this._deflate, this);
+ this._map.off('dragend', this._deflate, this);
+ }
+});
+
+L.deflate4 = function (options) {
+ return new L.Deflate4(options);
+};
\ No newline at end of file
diff --git a/cadasta/core/static/js/smap/L.TileLayer.GeoJSON.js b/cadasta/core/static/js/smap/L.TileLayer.GeoJSON.js
new file mode 100644
index 000000000..5e19b8cf7
--- /dev/null
+++ b/cadasta/core/static/js/smap/L.TileLayer.GeoJSON.js
@@ -0,0 +1,291 @@
+// Load data tiles from an AJAX data source
+L.TileLayer.Ajax = L.TileLayer.extend({
+ _requests: [],
+ _loadedfeatures: {},
+ _tiles: {},
+ _addTile: function (tilePoint) {
+ var tile = { datum: null, processed: false };
+ this._tiles[tilePoint.x + ':' + tilePoint.y] = {
+ el: tile,
+ coords: {
+ x: tilePoint.x,
+ y: tilePoint.y,
+ z: tilePoint.z
+ },
+ current: true
+ };
+ this._loadTile(tile, tilePoint);
+ },
+ // XMLHttpRequest handler; closure over the XHR object, the layer, and the tile
+ _xhrHandler: function (req, layer, tile, tilePoint) {
+ return function () {
+ if (req.readyState !== 4) {
+ return;
+ }
+ var s = req.status;
+ if ((s >= 200 && s < 300 && s != 204) || s === 304) {
+ tile.datum = JSON.parse(req.responseText);
+ layer._tileLoaded(tile, tilePoint);
+ } else {
+ layer._tileLoaded(tile, tilePoint);
+ }
+ };
+ },
+ // Load the requested tile via AJAX
+ _loadTile: function (tile, tilePoint) {
+ // ****
+ // _update has been refactored is no longer required?
+ // ****
+ // this._update(tilePoint);
+ var layer = this;
+ var req = new XMLHttpRequest();
+ this._requests.push(req);
+ req.onreadystatechange = this._xhrHandler(req, layer, tile, tilePoint);
+ req.open('GET', this.getTileUrl(tilePoint), true);
+ req.send();
+ },
+ _reset: function () {
+ L.TileLayer.prototype._reset.apply(this, arguments);
+ for (var i = 0; i < this._requests.length; i++) {
+ this._requests[i].abort();
+ }
+ this._requests = [];
+ },
+ _update: function () {
+ if (this._map && this._map._panTransition && this._map._panTransition._inProgress) { return; }
+ if (this._tilesToLoad < 0) { this._tilesToLoad = 0; }
+ L.TileLayer.prototype._update.apply(this, arguments);
+ }
+});
+
+
+L.TileLayer.GeoJSON = L.TileLayer.Ajax.extend({
+ // Store each GeometryCollection's layer by key, if options.unique function is present
+ _keyLayers: {},
+
+ // Used to calculate svg path string for clip path elements
+ _clipPathRectangles: {},
+
+ initialize: function (url, options, geojsonOptions) {
+ L.TileLayer.Ajax.prototype.initialize.call(this, url, options);
+ this.geojsonLayer = new L.GeoJSON(null, geojsonOptions);
+ },
+ onAdd: function (map) {
+ this._lazyTiles = new Tile(0, 0, 0, map.maxZoom);
+ this._map = map;
+ L.TileLayer.Ajax.prototype.onAdd.call(this, map);
+ map.addLayer(this.geojsonLayer);
+ },
+ onRemove: function (map) {
+ map.removeLayer(this.geojsonLayer);
+ L.TileLayer.Ajax.prototype.onRemove.call(this, map);
+ },
+ _reset: function () {
+ this.geojsonLayer.clearLayers();
+ this._keyLayers = {};
+ this._removeOldClipPaths();
+ L.TileLayer.Ajax.prototype._reset.apply(this, arguments);
+ },
+
+ _getUniqueId: function() {
+ return String(this._leaflet_id || ''); // jshint ignore:line
+ },
+
+ // Remove clip path elements from other earlier zoom levels
+ _removeOldClipPaths: function () {
+ for (var clipPathId in this._clipPathRectangles) {
+ var prefix = clipPathId.split('tileClipPath')[0];
+ if (this._getUniqueId() === prefix) {
+ var clipPathZXY = clipPathId.split('_').slice(1);
+ var zoom = parseInt(clipPathZXY[0], 10);
+ if (zoom !== this._map.getZoom()) {
+ var rectangle = this._clipPathRectangles[clipPathId];
+ this._map.removeLayer(rectangle);
+ var clipPath = document.getElementById(clipPathId);
+ if (clipPath !== null) {
+ clipPath.parentNode.removeChild(clipPath);
+ }
+ delete this._clipPathRectangles[clipPathId];
+ }
+ }
+ }
+ },
+
+ // Recurse LayerGroups and call func() on L.Path layer instances
+ _recurseLayerUntilPath: function (func, layer) {
+ if (layer instanceof L.Path) {
+ func(layer);
+ }
+ else if (layer instanceof L.LayerGroup) {
+ // Recurse each child layer
+ layer.getLayers().forEach(this._recurseLayerUntilPath.bind(this, func), this);
+ }
+ },
+
+ _clipLayerToTileBoundary: function (layer, tilePoint) {
+ // Only perform SVG clipping if the browser is using SVG
+ if (!L.Path.SVG) { return; }
+ if (!this._map) { return; }
+
+ if (!this._map._pathRoot) {
+ this._map._pathRoot = L.Path.prototype._createElement('svg');
+ this._map._panes.overlayPane.appendChild(this._map._pathRoot);
+ }
+ var svg = this._map._pathRoot;
+
+ // create the defs container if it doesn't exist
+ var defs = null;
+ if (svg.getElementsByTagName('defs').length === 0) {
+ defs = document.createElementNS(L.Path.SVG_NS, 'defs');
+ svg.insertBefore(defs, svg.firstChild);
+ }
+ else {
+ defs = svg.getElementsByTagName('defs')[0];
+ }
+
+ // Create the clipPath for the tile if it doesn't exist
+ var clipPathId = this._getUniqueId() + 'tileClipPath_' + tilePoint.z + '_' + tilePoint.x + '_' + tilePoint.y;
+ var clipPath = document.getElementById(clipPathId);
+ if (clipPath === null) {
+ clipPath = document.createElementNS(L.Path.SVG_NS, 'clipPath');
+ clipPath.id = clipPathId;
+
+ // Create a hidden L.Rectangle to represent the tile's area
+ var tileSize = this.options.tileSize,
+ nwPoint = tilePoint.multiplyBy(tileSize),
+ sePoint = nwPoint.add([tileSize, tileSize]),
+ nw = this._map.unproject(nwPoint),
+ se = this._map.unproject(sePoint);
+ this._clipPathRectangles[clipPathId] = new L.Rectangle(new L.LatLngBounds([nw, se]), {
+ opacity: 0,
+ fillOpacity: 0,
+ clickable: false,
+ noClip: true
+ });
+ this._map.addLayer(this._clipPathRectangles[clipPathId]);
+
+ // Add a clip path element to the SVG defs element
+ // With a path element that has the hidden rectangle's SVG path string
+ var path = document.createElementNS(L.Path.SVG_NS, 'path');
+ var pathString = this._clipPathRectangles[clipPathId].getPathString();
+ path.setAttribute('d', pathString);
+ clipPath.appendChild(path);
+ defs.appendChild(clipPath);
+ }
+
+ // Add the clip-path attribute to reference the id of the tile clipPath
+ this._recurseLayerUntilPath(function (pathLayer) {
+ pathLayer._container.setAttribute('clip-path', 'url(' + window.location.href + '#' + clipPathId + ')');
+ }, layer);
+ },
+
+ // Add a geojson object from a tile to the GeoJSON layer
+ // * If the options.unique function is specified, merge geometries into GeometryCollections
+ // grouped by the key returned by options.unique(feature) for each GeoJSON feature
+ // * If options.clipTiles is set, and the browser is using SVG, perform SVG clipping on each
+ // tile's GeometryCollection
+ addTileData: function (geojson, tilePoint) {
+ var features = L.Util.isArray(geojson) ? geojson : geojson.features,
+ i, len, feature;
+ if (features) {
+ $('#messages #loading').removeClass('hidden');
+ for (i = 0, len = features.length; i < len; i++) {
+ // Only add this if geometry or geometries are set and not null
+ feature = features[i];
+ if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
+ // ****
+ // Added to prevent reloading of already loaded SUs.
+ // ****
+ if (!this._loadedfeatures[features[i]['id']]){
+ this.addTileData(features[i], tilePoint);
+ this._loadedfeatures[features[i]['id']] = true;
+ }
+ }
+ }
+ $('#messages #loading').addClass('hidden');
+ return this;
+ }
+
+ var options = this.geojsonLayer.options;
+
+ if (options.filter && !options.filter(geojson)) { return; }
+
+ var parentLayer = this.geojsonLayer;
+ var incomingLayer = null;
+ if (this.options.unique && typeof(this.options.unique) === 'function') {
+ var key = this.options.unique(geojson);
+
+ // When creating the layer for a unique key,
+ // Force the geojson to be a geometry collection
+ if (!(key in this._keyLayers && geojson.geometry.type !== 'GeometryCollection')) {
+ geojson.geometry = {
+ type: 'GeometryCollection',
+ geometries: [geojson.geometry]
+ };
+ }
+
+ // Transform the geojson into a new Layer
+ try {
+ incomingLayer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng);
+ }
+ // Ignore GeoJSON objects that could not be parsed
+ catch (e) {
+ return this;
+ }
+
+ incomingLayer.feature = L.GeoJSON.asFeature(geojson);
+ // Add the incoming Layer to existing key's GeometryCollection
+ if (key in this._keyLayers) {
+ parentLayer = this._keyLayers[key];
+ parentLayer.feature.geometry.geometries.push(geojson.geometry);
+ }
+ // Convert the incoming GeoJSON feature into a new GeometryCollection layer
+ else {
+ this._keyLayers[key] = incomingLayer;
+ }
+ }
+ // Add the incoming geojson feature to the L.GeoJSON Layer
+ else {
+ // Transform the geojson into a new layer
+ try {
+ incomingLayer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng);
+ }
+ // Ignore GeoJSON objects that could not be parsed
+ catch (e) {
+ return this;
+ }
+ incomingLayer.feature = L.GeoJSON.asFeature(geojson);
+ }
+ incomingLayer.defaultOptions = incomingLayer.options;
+
+ this.geojsonLayer.resetStyle(incomingLayer);
+
+ if (options.onEachFeature) {
+ options.onEachFeature(geojson, incomingLayer);
+ }
+ parentLayer.addLayer(incomingLayer);
+
+ // If options.clipTiles is set and the browser is using SVG
+ // then clip the layer using SVG clipping
+ if (this.options.clipTiles) {
+ this._clipLayerToTileBoundary(incomingLayer, tilePoint);
+ }
+ return this;
+ },
+
+ _tileLoaded: function (tile, tilePoint) {
+ // ****
+ // _tileOnLoad has been refactored and requires a callback function
+ // ****
+ // L.TileLayer.Ajax.prototype._tileOnLoad.apply(this, tile);
+ if (tile.datum === null) { return null; }
+ this.addTileData(tile.datum, tilePoint);
+ this._lazyTiles.load(tilePoint.x, tilePoint.y, tilePoint.z);
+ },
+
+ _loadTile: function (tile, tilePoint) {
+ if (!this._lazyTiles.isLoaded(tilePoint.x, tilePoint.y, tilePoint.z)) {
+ L.TileLayer.Ajax.prototype._loadTile.call(this, tile, tilePoint);
+ }
+ },
+});
diff --git a/cadasta/core/static/js/smap/index.js b/cadasta/core/static/js/smap/index.js
index f3c533caa..4847de9eb 100644
--- a/cadasta/core/static/js/smap/index.js
+++ b/cadasta/core/static/js/smap/index.js
@@ -1,5 +1,10 @@
$(window).load(function () {
- var js_files = ['map.js']
+ var js_files = [
+ 'lazytiles.js',
+ 'L.Map.Deflate4.js',
+ 'L.TileLayer.GeoJSON.js',
+ 'map.js'
+ ];
var body = $('body')
for (i in js_files) {
body.append($(''));
diff --git a/cadasta/core/static/js/smap/lazytiles.js b/cadasta/core/static/js/smap/lazytiles.js
new file mode 100644
index 000000000..91ee73a8b
--- /dev/null
+++ b/cadasta/core/static/js/smap/lazytiles.js
@@ -0,0 +1,86 @@
+;(function(window, Math) {
+ function Tile(x, y, z, maxLevels, parent) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.loaded = false;
+ this.maxLevels = maxLevels || 18;
+ this.children = [];
+ this.parent = parent || null;
+ }
+
+ Tile.prototype.findPathToChild = function (x, y, z) {
+ const nleafs = Math.pow(2, z - this.z);
+ const l0x = nleafs * this.x;
+ const l0y = nleafs * this.y;
+ const xleft = (x < l0x + (nleafs / 2));
+ const yleft = (y < l0y + (nleafs / 2));
+ xt = xleft ? this.x * 2 : this.x * 2 + 1;
+ yt = yleft ? this.y * 2 : this.y * 2 + 1;
+
+ if (!this.children.length && this.z < this.maxLevels) {
+ this.children = [
+ new window.Tile(this.x * 2, this.y * 2, this.z + 1, this.maxLevels, this),
+ new window.Tile(this.x * 2, this.y * 2 + 1, this.z + 1, this.maxLevels, this),
+ new window.Tile(this.x * 2 + 1, this.y * 2, this.z + 1, this.maxLevels, this),
+ new window.Tile(this.x * 2 + 1, this.y * 2 + 1, this.z + 1, this.maxLevels, this)
+ ];
+ }
+
+ for (var i in this.children) {
+ const child = this.children[i];
+ if (child.x === x && child.y === y && child.z === z) {
+ return child;
+ }
+ }
+
+ for (var i in this.children) {
+ const child = this.children[i];
+ if (child.x === xt && child.y === yt) {
+ return child;
+ }
+ }
+ }
+
+ Tile.prototype.load = function(x, y, z) {
+ if (this.x === x && this.y === y && this.z === z && !this.loaded) {
+ this.loaded = true;
+ if (this.parent) { this.parent.loadFromChild(); }
+ } else {
+ const child = this.findPathToChild(x, y, z);
+ if (child) {child.load(x, y, z);}
+ }
+ }
+
+ Tile.prototype.loadFromChild = function() {
+ var loaded = true;
+ for (var i in this.children) {
+ if (!this.children[i].loaded) {
+ loaded = false;
+ }
+ }
+ if (loaded) {
+ this.loaded = true;
+ if (this.parent) { this.parent.loadFromChild(); }
+ }
+ }
+
+ Tile.prototype.isLoaded = function(x, y, z) {
+ if (this.loaded) {
+ return true;
+ }
+
+ if (this.z === z) {
+ return false;
+ }
+
+ const child = this.findPathToChild(x, y, z);
+ if (child) {
+ return child.isLoaded(x, y, z);
+ } else {
+ return false;
+ }
+ }
+
+ window.Tile = Tile;
+})(window, Math);
diff --git a/cadasta/core/static/js/smap/map.js b/cadasta/core/static/js/smap/map.js
index 6fa9d6747..8b06d0c9c 100644
--- a/cadasta/core/static/js/smap/map.js
+++ b/cadasta/core/static/js/smap/map.js
@@ -1,87 +1,83 @@
var SMap = (function() {
- var map = L.map('mapid');
- var layerscontrol = L.control.layers().addTo(map);
+ var map = L.map('mapid');
+ var layerscontrol = L.control.layers().addTo(map);
+ var prev_urls = [];
+ var loaded_features = {};
- function add_tile_layers() {
- for (var i = 0, n = layers.length; i < n; i++) {
- var options = L.Util.extend(layers[i]['attrs']);
- var layer = {name: layers[i]['label'], url: layers[i]['url'], options: options};
+ var geojsonTileLayer = new L.TileLayer.GeoJSON(
+ url,
+ {
+ clipTiles: true,
+ unique: function (feature) {return feature.id;}
+ },
+ {
+ style: { weight: 2 },
+ onEachFeature: function(feature, layer) {
+ if (options.trans) {
+ layer.bindPopup("" +
+ "
Location" +
+ feature.properties.type + "
" +
+ "");
+ }
+ }
+ });
- var l = L.tileLayer(layer.url, layer.options);
- layerscontrol.addBaseLayer(l, layer.name);
+ function add_tile_layers() {
+ for (var i = 0, n = layers.length; i < n; i++) {
+ var attrs = L.Util.extend(layers[i]['attrs']);
+ var layer = {name: layers[i]['label'], url: layers[i]['url'], options: attrs};
+ var l = L.tileLayer(layer.url, layer.options);
+ layerscontrol.addBaseLayer(l, layer.name);
- if (i === 0) {
- l.addTo(map);
- }
+ if (i === 0) {
+ l.addTo(map);
}
}
+ }
- add_tile_layers();
+ add_tile_layers();
+ // var features = L.deflate4({minSize: 20}).addTo(map);
+ // features.addLayer(geojsonTileLayer);
+ map.addLayer(geojsonTileLayer);
- function load_project_extent() {
- if (options.projectExtent) {
- var boundary = L.geoJson(
- options.projectExtent, {
- style: {
- stroke: true,
- color: "#0e305e",
- weight: 2,
- dashArray: "5, 5",
- opacity: 1,
- fill: false,
- clickable: false,
- }
+ function load_project_extent() {
+ if (options.projectExtent) {
+ var boundary = L.geoJson(
+ options.projectExtent, {
+ style: {
+ stroke: true,
+ color: "#0e305e",
+ weight: 2,
+ dashArray: "5, 5",
+ opacity: 1,
+ fill: false,
+ clickable: false,
}
- );
- boundary.addTo(map);
- projectBounds = boundary.getBounds();
- }
+ }
+ );
+ boundary.addTo(map);
+ projectBounds = boundary.getBounds();
if (options.fitBounds === 'project') {
map.fitBounds(projectBounds);
- } else if (options.fitBounds !== 'locations') {
- map.fitBounds([[-45.0, -180.0], [45.0, 180.0]]);
}
+ } else {
+ map.fitBounds([[-45.0, -180.0], [45.0, 180.0]]);
}
+ }
- load_project_extent()
-
- function render_features(){
- var geoJson = L.geoJson(null, {
- style: { weight: 2 },
- onEachFeature: function(feature, layer) {
- if (options.trans) {
- layer.bindPopup("" +
- "
Location" +
- feature.properties.type + "
" +
- "");
- }
- }
- });
+ load_project_extent()
- function load_features(request_url) {
- $('#messages #loading').removeClass('hidden');
- $.get(request_url, function(response) {
- geoJson.addData(response);
- if (response.next) {
- load_features(response.next);
- } else {
- $('#messages #loading').addClass('hidden');
- if (options.fitBounds === 'locations') {
- var bounds = geoJson.getBounds();
- if (bounds.isValid()) {
- map.fitBounds(bounds);
- }
- }
- }
- });
+ function load_features() {;
+ if (options.fitBounds === 'locations') {
+ var bounds = geojsonTileLayer.geojsonLayer.getBounds();
+ if (bounds.isValid()) {
+ map.fitBounds(bounds);
}
-
- geoJson.addTo(map);
- load_features(url);
+ }
}
- render_features()
+ load_features();
function render_spatial_resource(){
$.ajax(fetch_spatial_resources).done(function(data){
diff --git a/cadasta/spatial/urls/async.py b/cadasta/spatial/urls/async.py
index e41096b7d..ca67449e8 100644
--- a/cadasta/spatial/urls/async.py
+++ b/cadasta/spatial/urls/async.py
@@ -8,6 +8,10 @@
r'^$',
async.SpatialUnitList.as_view(),
name='list'),
+ url(
+ r'^tiled/(?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+)/$',
+ async.SpatialUnitTiles.as_view(),
+ name='tiled'),
]
@@ -16,4 +20,8 @@
r'^organizations/(?P[-\w]+)/projects/'
'(?P[-\w]+)/spatial/',
include(urls)),
+ # url(urls.tilepath(r'^organizations/(?P[-\w]+)/projects/'
+ # '(?P[-\w]+)/spatial/'),
+ # async.SpatialUnitList.as_view(),
+ # name='location-tiles'),
]
diff --git a/cadasta/spatial/views/async.py b/cadasta/spatial/views/async.py
index a2c48cac8..2b83c500f 100644
--- a/cadasta/spatial/views/async.py
+++ b/cadasta/spatial/views/async.py
@@ -1,3 +1,5 @@
+import math
+from django.contrib.gis.geos import Polygon
from tutelary.mixins import APIPermissionRequiredMixin
from rest_framework import generics
from rest_framework_gis.pagination import GeoJsonPagination
@@ -6,6 +8,24 @@
from .. import serializers
+def deg2num(lat_deg, lon_deg, zoom):
+ lat_rad = math.radians(lat_deg)
+ n = 2.0 ** zoom
+ xtile = int((lon_deg + 180.0) / 360.0 * n)
+ ytile = int(
+ (1.0 - math.log(math.tan(lat_rad) +
+ (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n)
+ return [xtile, ytile]
+
+
+def num2deg(xtile, ytile, zoom):
+ n = 2.0 ** zoom
+ lon_deg = xtile / n * 360.0 - 180.0
+ lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
+ lat_deg = math.degrees(lat_rad)
+ return [lon_deg, lat_deg]
+
+
class Paginator(GeoJsonPagination):
page_size = 1000
@@ -34,3 +54,40 @@ def get_queryset(self, *args, **kwargs):
def get_perms_objects(self):
return [self.get_project()]
+
+
+class SpatialUnitTiles(APIPermissionRequiredMixin,
+ mixins.SpatialQuerySetMixin,
+ generics.ListAPIView):
+
+ serializer_class = serializers.SpatialUnitGeoJsonSerializer
+
+ def get_actions(self, request):
+ if self.get_project().archived:
+ return ['project.view_archived', 'spatial.list']
+ if self.get_project().public():
+ return ['project.view', 'spatial.list']
+ else:
+ return ['project.view_private', 'spatial.list']
+
+ permission_required = {
+ 'GET': get_actions
+ }
+
+ def get_queryset(self, *args, **kwargs):
+ queryset = super().get_queryset(*args, **kwargs)
+ x = int(self.kwargs['x'])
+ y = int(self.kwargs['y'])
+ zoom = int(self.kwargs['z'])
+
+ bbox = num2deg(xtile=x, ytile=y, zoom=zoom)
+ bbox.extend(num2deg(xtile=x+1, ytile=y+1, zoom=zoom))
+ bbox = Polygon.from_bbox(bbox)
+ final_queryset = queryset.filter(
+ geometry__intersects=bbox).exclude(
+ id=self.request.GET.get('exclude'))
+
+ return final_queryset
+
+ def get_perms_objects(self):
+ return [self.get_project()]
diff --git a/cadasta/templates/organization/project_dashboard.html b/cadasta/templates/organization/project_dashboard.html
index a2252aea3..6ce6dc534 100644
--- a/cadasta/templates/organization/project_dashboard.html
+++ b/cadasta/templates/organization/project_dashboard.html
@@ -21,7 +21,6 @@
open: "{% trans 'Open location' %}"
};
- switch_layer_controls(map, options);
{% if project.extent %}
var projectExtent = {{ project.extent.geojson|safe }};
@@ -29,9 +28,15 @@
var projectExtent = null;
{% endif %}
+ options.project_slug = '{{ project.slug }}'
+ options.org_slug = '{{ project.organization.slug }}'
+ options.trans = trans
+
+ switch_layer_controls(map, options);
+
renderFeatures(map,
- '{% url "async:spatial:list" project.organization.slug project.slug %}',
- {projectExtent: projectExtent, trans: trans, fitBounds: 'locations'});
+ '{% url "async:spatial:tiled" project.organization.slug project.slug 0 0 0 %}',
+ {projectExtent: projectExtent, trans: trans, fitBounds: 'project'});
var orgSlug = '{{ project.organization.slug }}';
var projectSlug = '{{ project.slug }}';
diff --git a/cadasta/templates/organization/project_map.html b/cadasta/templates/organization/project_map.html
index 16255af13..8799e293e 100644
--- a/cadasta/templates/organization/project_map.html
+++ b/cadasta/templates/organization/project_map.html
@@ -13,13 +13,16 @@
{% block extra_script %}
+
+
-
+