Merge pull request #69 from amercader/master

[#64,view/map][m]: Preliminary work on the Leaflet-based map Data View from @amercader.
This commit is contained in:
Rufus Pollock 2012-04-04 23:59:18 -07:00
commit 0155d4dec5
13 changed files with 567 additions and 7 deletions

View File

@ -13,8 +13,13 @@
<link rel="stylesheet" href="../vendor/bootstrap/2.0.2/css/bootstrap.css">
<link rel="stylesheet" href="../css/data-explorer.css">
<link rel="stylesheet" href="../css/graph-flot.css">
<link rel="stylesheet" href="../css/map.css">
<link rel="stylesheet" href="style/demo.css">
<link rel="stylesheet" href="../vendor/bootstrap/2.0.2/css/bootstrap-responsive.css">
<link rel="stylesheet" href="../vendor/leaflet/leaflet.css">
<!--[if lte IE 8]>
<link rel="stylesheet" href="../vendor/leaflet/leaflet.ie.css" />
<![endif]-->
<!-- 3rd party libraries -->
<script type="text/javascript" src="../vendor/jquery-1.7.1.js"></script>
@ -24,6 +29,7 @@
<script type="text/javascript" src="../vendor/jquery.flot-0.7.js"></script>
<script type="text/javascript" src="../vendor/jquery.mustache.js"></script>
<script type="text/javascript" src="../vendor/bootstrap/2.0.2/bootstrap.js"></script>
<script type="text/javascript" src="../vendor/leaflet/leaflet.js"></script>
<!-- recline library -->
<!-- in normal use would just the single recline.js library file. However, for testing it
@ -40,6 +46,7 @@
<script type="text/javascript" src="../src/view.js"></script>
<script type="text/javascript" src="../src/view-grid.js"></script>
<script type="text/javascript" src="../src/view-flot-graph.js"></script>
<script type="text/javascript" src="../src/view-map.js"></script>
<script type="text/javascript" src="../src/view-transform-dialog.js"></script>
<!-- non-library javascript specific to this demo -->

View File

@ -61,7 +61,15 @@ function standardViews(dataset) {
view: new recline.View.FlotGraph({
model: dataset
})
},
{
id: 'map',
label: 'Map',
view: new recline.View.Map({
model: dataset
})
}
];
return views;
}
@ -75,14 +83,14 @@ function localDataset() {
, name: '1-my-test-dataset'
, id: datasetId
},
fields: [{id: 'x'}, {id: 'y'}, {id: 'z'}, {id: 'country'}, {id: 'label'}],
fields: [{id: 'x'}, {id: 'y'}, {id: 'z'}, {id: 'country'}, {id: 'label'},{id: 'lat'},{id: 'lon'}],
documents: [
{id: 0, x: 1, y: 2, z: 3, country: 'DE', label: 'first'}
, {id: 1, x: 2, y: 4, z: 6, country: 'UK', label: 'second'}
, {id: 2, x: 3, y: 6, z: 9, country: 'US', label: 'third'}
, {id: 3, x: 4, y: 8, z: 12, country: 'UK', label: 'fourth'}
, {id: 4, x: 5, y: 10, z: 15, country: 'UK', label: 'fifth'}
, {id: 5, x: 6, y: 12, z: 18, country: 'DE', label: 'sixth'}
{id: 0, x: 1, y: 2, z: 3, country: 'DE', label: 'first', lat:52.56, lon:13.40}
, {id: 1, x: 2, y: 4, z: 6, country: 'UK', label: 'second', lat:54.97, lon:-1.60}
, {id: 2, x: 3, y: 6, z: 9, country: 'US', label: 'third', lat:40.00, lon:-75.5}
, {id: 3, x: 4, y: 8, z: 12, country: 'UK', label: 'fourth', lat:57.27, lon:-6.20}
, {id: 4, x: 5, y: 10, z: 15, country: 'UK', label: 'fifth', lat:51.58, lon:0}
, {id: 5, x: 6, y: 12, z: 18, country: 'DE', label: 'sixth', lat:51.04, lon:7.9}
]
};
var backend = new recline.Backend.Memory();

4
css/map.css Normal file
View File

@ -0,0 +1,4 @@
.data-map-container .map {
height: 500px;
}

164
src/view-map.js Normal file
View File

@ -0,0 +1,164 @@
this.recline = this.recline || {};
this.recline.View = this.recline.View || {};
(function($, my) {
my.Map = Backbone.View.extend({
tagName: 'div',
className: 'data-map-container',
latitudeFieldNames: ['lat','latitude'],
longitudeFieldNames: ['lon','longitude'],
geometryFieldNames: ['geom','the_geom','geometry','spatial'],
//TODO: In case we want to change the default markers
/*
markerOptions: {
radius: 5,
color: 'grey',
fillColor: 'orange',
weight: 2,
opacity: 1,
fillOpacity: 1
},
*/
template: ' \
<div class="panel map"> \
</div> \
',
initialize: function(options, config) {
var self = this;
this.el = $(this.el);
this.render();
this.model.bind('change', function() {
self._setupGeometryField();
});
this.mapReady = false;
},
render: function() {
var self = this;
htmls = $.mustache(this.template, this.model.toTemplateJSON());
$(this.el).html(htmls);
this.$map = this.el.find('.panel.map');
this.model.bind('query:done', function() {
if (!self.geomReady){
self._setupGeometryField();
}
if (!self.mapReady){
self._setupMap();
}
self.redraw()
});
return this;
},
redraw: function(){
var self = this;
if (this.geomReady){
if (this.model.currentDocuments.length > 0){
this.features.clearLayers();
var bounds = new L.LatLngBounds();
this.model.currentDocuments.forEach(function(doc){
var feature = self._getGeometryFromDocument(doc);
if (feature){
// Build popup contents
// TODO: mustache?
html = ''
for (key in doc.attributes){
html += '<div><strong>' + key + '</strong>: '+ doc.attributes[key] + '</div>'
}
feature.properties = {popupContent: html};
self.features.addGeoJSON(feature);
// TODO: bounds and center map
}
});
}
}
},
_getGeometryFromDocument: function(doc){
if (this.geomReady){
if (this._geomFieldName){
// We assume that the contents of the field are a valid GeoJSON object
return doc.attributes[this._geomFieldName];
} else if (this._lonFieldName && this._latFieldName){
// We'll create a GeoJSON like point object from the two lat/lon fields
return {
type: 'Point',
coordinates: [
doc.attributes[this._lonFieldName],
doc.attributes[this._latFieldName],
]
}
}
return null;
}
},
_setupGeometryField: function(){
var geomField, latField, lonField;
// Check if there is a field with GeoJSON geometries or alternatively,
// two fields with lat/lon values
this._geomFieldName = this._checkField(this.geometryFieldNames);
this._latFieldName = this._checkField(this.latitudeFieldNames);
this._lonFieldName = this._checkField(this.longitudeFieldNames);
// TODO: Allow users to choose the fields
this.geomReady = (this._geomFieldName || (this._latFieldName && this._lonFieldName));
},
_checkField: function(fieldNames){
var field;
for (var i = 0; i < fieldNames.length; i++){
field = this.model.fields.get(fieldNames[i]);
if (field) return field.id;
}
return null;
},
_setupMap: function(){
this.map = new L.Map(this.$map.get(0));
// MapQuest OpenStreetMap base map
var mapUrl = "http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png";
var osmAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="http://developer.mapquest.com/content/osm/mq_logo.png">';
var bg = new L.TileLayer(mapUrl, {maxZoom: 18, attribution: osmAttribution ,subdomains: '1234'});
this.map.addLayer(bg);
// Layer to hold the features
this.features = new L.GeoJSON();
this.features.on('featureparse', function (e) {
if (e.properties && e.properties.popupContent){
e.layer.bindPopup(e.properties.popupContent);
}
});
this.map.addLayer(this.features);
this.map.setView(new L.LatLng(0, 0), 2);
this.mapReady = true;
}
});
})(jQuery, recline.View);

BIN
vendor/leaflet/images/layers.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
vendor/leaflet/images/marker-shadow.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
vendor/leaflet/images/marker.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
vendor/leaflet/images/popup-close.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
vendor/leaflet/images/zoom-in.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

BIN
vendor/leaflet/images/zoom-out.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

323
vendor/leaflet/leaflet.css vendored Normal file
View File

@ -0,0 +1,323 @@
/* required styles */
.leaflet-map-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-pane,
.leaflet-overlay-pane,
.leaflet-shadow-pane,
.leaflet-marker-pane,
.leaflet-popup-pane,
.leaflet-overlay-pane svg,
.leaflet-zoom-box,
.leaflet-image-layer { /* TODO optimize classes */
position: absolute;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile-pane, .leaflet-container {
-webkit-transform: translate3d(0,0,0);
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
.leaflet-clickable {
cursor: pointer;
}
.leaflet-container img {
max-width: none !important;
}
.leaflet-tile-pane { z-index: 2; }
.leaflet-objects-pane { z-index: 3; }
.leaflet-overlay-pane { z-index: 4; }
.leaflet-shadow-pane { z-index: 5; }
.leaflet-marker-pane { z-index: 6; }
.leaflet-popup-pane { z-index: 7; }
.leaflet-zoom-box {
width: 0;
height: 0;
}
.leaflet-tile {
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
a.leaflet-active {
outline: 2px solid orange;
}
/* Leaflet controls */
.leaflet-control {
position: relative;
z-index: 7;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
.leaflet-control-zoom, .leaflet-control-layers {
-moz-border-radius: 7px;
-webkit-border-radius: 7px;
border-radius: 7px;
}
.leaflet-control-zoom {
padding: 5px;
background: rgba(0, 0, 0, 0.25);
}
.leaflet-control-zoom a {
background-color: rgba(255, 255, 255, 0.75);
}
.leaflet-control-zoom a, .leaflet-control-layers a {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-control-zoom a {
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
width: 19px;
height: 19px;
}
.leaflet-control-zoom a:hover {
background-color: #fff;
}
.leaflet-big-buttons .leaflet-control-zoom a {
width: 27px;
height: 27px;
}
.leaflet-control-zoom-in {
background-image: url(images/zoom-in.png);
margin-bottom: 5px;
}
.leaflet-control-zoom-out {
background-image: url(images/zoom-out.png);
}
.leaflet-control-layers {
-moz-box-shadow: 0 0 7px #999;
-webkit-box-shadow: 0 0 7px #999;
box-shadow: 0 0 7px #999;
background: #f8f8f9;
}
.leaflet-control-layers a {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-big-buttons .leaflet-control-layers a {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
color: #333;
background: #fff;
}
.leaflet-control-layers input {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
.leaflet-container .leaflet-control-attribution {
margin: 0;
padding: 0 5px;
font: 11px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
color: #333;
background-color: rgba(255, 255, 255, 0.7);
-moz-box-shadow: 0 0 7px #ccc;
-webkit-box-shadow: 0 0 7px #ccc;
box-shadow: 0 0 7px #ccc;
}
/* Fade animations */
.leaflet-fade-anim .leaflet-tile {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
-o-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-tile-loaded {
opacity: 1;
}
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
-o-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-objects-pane {
visibility: hidden;
}
/* Popup layout */
.leaflet-popup {
position: absolute;
text-align: center;
-webkit-transform: translate3d(0,0,0);
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
}
.leaflet-popup-content {
margin: 19px;
}
.leaflet-popup-tip-container {
margin: 0 auto;
width: 40px;
height: 16px;
position: relative;
overflow: hidden;
}
.leaflet-popup-tip {
width: 15px;
height: 15px;
padding: 1px;
margin: -8px auto 0;
-moz-transform: rotate(45deg);
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-close-button {
position: absolute;
top: 9px;
right: 9px;
width: 10px;
height: 10px;
overflow: hidden;
}
.leaflet-popup-content p {
margin: 18px 0;
}
/* Visual appearance */
.leaflet-container {
background: #ddd;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #05f;
background: white;
opacity: 0.5;
}
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
background: white;
box-shadow: 0 1px 10px #888;
-moz-box-shadow: 0 1px 10px #888;
-webkit-box-shadow: 0 1px 14px #999;
}
.leaflet-popup-content-wrapper {
-moz-border-radius: 20px;
-webkit-border-radius: 20px;
border-radius: 20px;
}
.leaflet-popup-content {
font: 12px/1.4 "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.leaflet-popup-close-button {
background: white url(images/popup-close.png);
}

48
vendor/leaflet/leaflet.ie.css vendored Normal file
View File

@ -0,0 +1,48 @@
.leaflet-tile {
filter: inherit;
}
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
.leaflet-control {
display: inline;
}
.leaflet-popup-tip {
width: 21px;
_width: 27px;
margin: 0 auto;
_margin-top: -3px;
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
}
.leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
border: 1px solid #bbb;
}
.leaflet-control-zoom {
filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#3F000000',EndColorStr='#3F000000');
}
.leaflet-control-zoom a {
background-color: #eee;
}
.leaflet-control-zoom a:hover {
background-color: #fff;
}
.leaflet-control-layers-toggle {
}
.leaflet-control-attribution, .leaflet-control-layers {
background: white;
}

6
vendor/leaflet/leaflet.js vendored Normal file

File diff suppressed because one or more lines are too long