Skip to content

Commit

Permalink
Initial commit for Leaflet-Solr-Heatmap, a plugin for visualization o…
Browse files Browse the repository at this point in the history
…f spatial data in Solr 5.x
  • Loading branch information
mejackreed committed Jun 29, 2015
0 parents commit 4dadcfe
Show file tree
Hide file tree
Showing 8 changed files with 359 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
bower_components
node_modules
28 changes: 28 additions & 0 deletions Gruntfile.js
@@ -0,0 +1,28 @@
/*global module:false*/
module.exports = function(grunt) {

// Project configuration.
grunt.initConfig({
// Metadata.
pkg: grunt.file.readJSON('package.json'),
watch: {
files: ['**/*.js']
},
connect: {
server: {
options: {
port: 8000,
base: './'
}
}
}
});

// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-connect');

// Default task.
grunt.registerTask('default', ['connect', 'watch']);

};
28 changes: 28 additions & 0 deletions bower.json
@@ -0,0 +1,28 @@
{
"name": "leaflet-solr-heatmap",
"version": "0.0.0",
"description": "A Leaflet plugin using Solr 5.x heatmap facets",
"main": "leafletSolrHeatmap.js",
"keywords": [
"leaflet",
"solr",
"heatmap"
],
"authors": [
"Jack Reed"
],
"license": "MIT",
"homepage": "https://github.com/mejackreed/leaflet-solr-heatmap",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"jquery": "~2.1.4",
"leaflet.markerclusterer": "~0.4.0",
"leaflet": "~0.7.3"
}
}
16 changes: 16 additions & 0 deletions example/example.js
@@ -0,0 +1,16 @@
var map = L.map('map').setView([0, 0], 2);

var layer = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a>'
}).addTo(map);

function onEachFeature(feature, layer) {
var count = feature.properties.count.toString();
layer.bindPopup(count);
}

var solr = L.solr('http://127.0.0.1:8983/solr/gettingstarted', {
onEachFeature: onEachFeature,
type: 'geojsonGrid'
// type: 'clusters'
}).addTo(map);
18 changes: 18 additions & 0 deletions example/index.html
@@ -0,0 +1,18 @@
<html>
<head>
<link rel="stylesheet" href="../bower_components/leaflet/dist/leaflet.css" />
<link rel="stylesheet" href="../bower_components/leaflet.markerclusterer/dist/MarkerCluster.Default.css" />
<link rel='stylesheet' href='style.css' />
<script src="../bower_components/leaflet/dist/leaflet.js"></script>
<script src="../bower_components/leaflet.markerclusterer/dist/leaflet.markercluster.js"></script>
<script src="../bower_components/jquery/dist/jquery.min.js"></script>
</head>
<body>
<div id='map'></div>
<div id='responseTime'></div>
<div id='numDocs'></div>
<div id='renderTime'></div>
<script src='../leafletSolrHeatmap.js'></script>
<script src='example.js'></script>
</body>
</html>
4 changes: 4 additions & 0 deletions example/style.css
@@ -0,0 +1,4 @@
#map {
height: 500px;
width: 1000px;
}
252 changes: 252 additions & 0 deletions leafletSolrHeatmap.js
@@ -0,0 +1,252 @@
L.Solr = L.GeoJSON.extend({
options: {
field: 'loc_srpt',
solrRequestHandler: 'select'
},

initialize: function(url, options) {
var _this = this;
options = L.setOptions(_this, options);
_this._solrUrl = url;
_this._layers = {};
_this._getData();
map.on('moveend', function() {
_this._clearLayers();
_this._getData();
});
},

_computeHeatmapObject: function(data) {
var _this = this;
_this.facetHeatmap = {},
facetHeatmapArray = data.facet_counts.facet_heatmaps[this.options.field];

// Convert array to an object
$.each(facetHeatmapArray, function(index, value) {
if ((index + 1) % 2 !== 0) {
// Set object keys for even items
_this.facetHeatmap[value] = '';
}else {
// Set object values for odd items
_this.facetHeatmap[facetHeatmapArray[index - 1]] = value;
}
});

this._computeIntArrays();
},

_clearLayers: function() {
var _this = this;

switch (_this.options.type) {
case 'geojsonGrid':
_this.clearLayers();
break;
case 'clusters':
_this.clusterMarkers.clearLayers();
break;
}
},

_createGeojson: function() {
var _this = this;
var geojson = {};

geojson.type = 'FeatureCollection';
geojson.features = [];

$.each(_this.facetHeatmap.counts_ints2D, function(row, value) {
if (value === null) {
return;
}

$.each(value, function(column, val) {
if (val === 0) {
return;
}

var newFeature = {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [
[
[_this._minLng(column), _this._minLat(row)],
[_this._minLng(column), _this._maxLat(row)],
[_this._maxLng(column), _this._maxLat(row)],
[_this._maxLng(column), _this._minLat(row)],
[_this._minLng(column), _this._minLat(row)]
]
]
},
properties: {
count: val
}
};
geojson.features.push(newFeature);
});
});

_this.addData(geojson);
_this._styleByCount();
_this._showRenderTime();
},

_showRenderTime: function() {
var _this = this,
renderTime = 'Render time: ' + (Date.now() - _this.renderStart) + ' ms';
$('#renderTime').html(renderTime);
},

_createClusters: function() {
var _this = this;

_this.clusterMarkers = new L.MarkerClusterGroup({
maxClusterRadius: 140
});

$.each(_this.facetHeatmap.counts_ints2D, function(row, value) {
if (value === null) {
return;
}

$.each(value, function(column, val) {
if (val === 0) {
return;
}

var bounds = new L.latLngBounds([
[_this._minLat(row), _this._minLng(column)],
[_this._maxLat(row), _this._maxLng(column)]
]);
_this.clusterMarkers.addLayer(new L.Marker(bounds.getCenter(), {
count: val
}).bindPopup(val.toString()));
});
});

map.addLayer(_this.clusterMarkers);
_this._showRenderTime();
},

_computeIntArrays: function() {
var _this = this;

_this.lengthX = (_this.facetHeatmap.maxX - _this.facetHeatmap.minX) / _this.facetHeatmap.columns;
_this.lengthY = (_this.facetHeatmap.maxY - _this.facetHeatmap.minY) / _this.facetHeatmap.rows;

switch (_this.options.type) {
case 'geojsonGrid':
_this._createGeojson();
break;
case 'clusters':
_this._createClusters();
break;
}
},

_styleByCount: function() {
var _this = this;
_this.eachLayer(function(layer) {
var ratio = ((layer.feature.properties.count) / Math.log1p(_this.docsCount));
layer.setStyle({
fillColor: '#F00',
fillOpacity: ratio,
weight: 1
});
});
},

_minLng: function(column) {
return this.facetHeatmap.minX + (this.lengthX * column);
},

_minLat: function(row) {
return this.facetHeatmap.maxY - (this.lengthY * row) - this.lengthY;
},

_maxLng: function(column) {
return this.facetHeatmap.minX + (this.lengthX * column) + this.lengthX;
},

_maxLat: function(row) {
return this.facetHeatmap.maxY - (this.lengthY * row);
},

_getData: function() {
var _this = this;
var startTime = Date.now();
$.ajax({
url: _this._solrUrl + _this._solrQuery(),
dataType: 'JSONP',
data: {
q: '*:*',
wt: 'json',
facet: true,
'facet.heatmap': _this.options.field,
'facet.heatmap.geom': _this._mapViewToWkt(),
fq: _this.options.field + _this._mapViewToEnvelope()
},
jsonp: 'json.wrf',
success: function(data) {
var totalTime = 'Solr response time: ' + (Date.now() - startTime) + ' ms';
$('#responseTime').html(totalTime);
_this.docsCount = data.response.numFound;
$('#numDocs').html('Number of docs: ' + _this.docsCount);
_this.renderStart = Date.now();
_this._computeHeatmapObject(data);
}
});
},

_mapViewToEnvelope: function() {
var bounds = map.getBounds();
return ':"Intersects(ENVELOPE(' + bounds.getWest() + ', ' + bounds.getEast() + ', ' + bounds.getNorth() + ', ' + bounds.getSouth() + '))"';
},

_mapViewToWkt: function() {
var bounds = map.getBounds();
return '["' + bounds.getWest() + ' ' + bounds.getSouth() + '" TO "' + bounds.getEast() + ' ' + bounds.getNorth() + '"]';
},

_solrQuery: function() {
return '/' + this.options.solrRequestHandler + '?' + this.options.field;
}
});

L.solr = function(url, options) {
return new L.Solr(url, options);
};

L.LatLngBounds.prototype.getWest = function() {
var west = this._southWest.lng;
return west < -180 ? -180 : west;
};

L.LatLngBounds.prototype.getEast = function() {
var east = this._northEast.lng;
return east > 180 ? 180 : east;
};

L.MarkerCluster.prototype.initialize = function(group, zoom, a, b) {

L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0), { icon: this });

this._group = group;
this._zoom = zoom;

this._markers = [];
this._childClusters = [];
this._childCount = 0;
this._iconNeedsUpdate = true;

this._bounds = new L.LatLngBounds();

if (a) {
this._addChild(a);
}
if (b) {
this._addChild(b);
this._childCount = b.options.count;
}
};
11 changes: 11 additions & 0 deletions package.json
@@ -0,0 +1,11 @@
{
"engines": {
"node": ">= 0.10.0"
},
"devDependencies": {
"grunt": "^0.4.5",
"grunt-bower-task": "^0.4.0",
"grunt-contrib-connect": "^0.10.1",
"grunt-contrib-watch": "~0.6.1"
}
}

0 comments on commit 4dadcfe

Please sign in to comment.