Skip to content
Snippets Groups Projects
Commit 19e55323 authored by Tom Robinson's avatar Tom Robinson
Browse files

Scalable pin maps

parent c92d0476
Branches
Tags
No related merge requests found
......@@ -73,6 +73,16 @@
z-index: 2;
}
/* Google Maps widgets */
.DashCard .gm-style-mtc,
.DashCard .gm-bundled-control {
opacity: 0.01;
transition: opacity 0.3s linear;
}
.DashCard:hover .gm-style-mtc,
.DashCard:hover .gm-bundled-control {
opacity: 1;
}
.Dash--editing .DashCard .Card {
background: #fff;
......
import React, { Component, PropTypes } from "react";
/*global google*/
import CardRenderer from "./components/CardRenderer.jsx";
import React, { Component, PropTypes } from "react";
import ReactDOM from "react-dom";
import { getSettingsForVisualization, setLatitudeAndLongitude } from "metabase/lib/visualization_settings";
import { hasLatitudeAndLongitudeColumns } from "metabase/lib/schema_metadata";
import { LatitudeLongitudeError } from "metabase/visualizations/lib/errors";
import _ from "underscore";
export default class PinMap extends Component {
static displayName = "Pin Map";
static identifier = "pin_map";
......@@ -19,9 +23,92 @@ export default class PinMap extends Component {
if (!hasLatitudeAndLongitudeColumns(cols)) { throw new LatitudeLongitudeError(); }
}
constructor(props, context) {
super(props, context);
this.state = {};
_.bindAll(this, "updateMapZoom", "updateMapCenter");
}
updateMapCenter(lat, lon) {
this.props.onUpdateVisualizationSetting(["map", "center_latitude"], lat);
this.props.onUpdateVisualizationSetting(["map", "center_longitude"], lat);
}
updateMapZoom(zoom) {
this.props.onUpdateVisualizationSetting(["map", "zoom"], zoom);
}
getTileUrl(settings, coord, zoom) {
let query = this.props.series[0].card.dataset_query;
let latitude_dataset_col_index = settings.map.latitude_dataset_col_index;
let longitude_dataset_col_index = settings.map.longitude_dataset_col_index;
let latitude_source_table_field_id = settings.map.latitude_source_table_field_id;
let longitude_source_table_field_id = settings.map.longitude_source_table_field_id;
if (latitude_dataset_col_index == null || longitude_dataset_col_index == null) {
return;
}
if (latitude_source_table_field_id == null || longitude_source_table_field_id == null) {
throw ("Map ERROR: latitude and longitude column indices must be specified");
}
if (latitude_dataset_col_index == null || longitude_dataset_col_index == null) {
throw ("Map ERROR: unable to find specified latitude / longitude columns in source table");
}
return '/api/tiles/' + zoom + '/' + coord.x + '/' + coord.y + '/' +
latitude_source_table_field_id + '/' + longitude_source_table_field_id + '/' +
latitude_dataset_col_index + '/' + longitude_dataset_col_index + '/' +
'?query=' + encodeURIComponent(JSON.stringify(query))
}
componentDidMount() {
try {
let element = ReactDOM.findDOMNode(this);
let { card, data } = this.props.series[0];
let settings = card.visualization_settings;
settings = getSettingsForVisualization(settings, "pin_map");
settings = setLatitudeAndLongitude(settings, data.cols);
let mapOptions = {
zoom: settings.map.zoom,
center: new google.maps.LatLng(settings.map.center_latitude, settings.map.center_longitude),
mapTypeId: google.maps.MapTypeId.MAP,
scrollwheel: false
};
let markerImageMapType = new google.maps.ImageMapType({
getTileUrl: this.getTileUrl.bind(this, settings),
tileSize: new google.maps.Size(256, 256)
});
let map = this.map = new google.maps.Map(element, mapOptions);
map.overlayMapTypes.push(markerImageMapType);
map.addListener("center_changed", () => {
let center = map.getCenter();
this.updateMapCenter(center.lat(), center.lng());
});
map.addListener("zoom_changed", () => {
this.updateMapZoom(map.getZoom());
});
} catch (err) {
console.error(err);
this.props.onRenderError(err.message || err);
}
}
componentDidUpdate() {
google.maps.event.trigger(this.map, "resize");
}
render() {
return (
<CardRenderer {...this.props} chartType="pin_map" />
);
return <div {...this.props}>x</div>;
}
}
......@@ -6,9 +6,7 @@ import AbsoluteContainer from "metabase/components/AbsoluteContainer.jsx";
import * as charting from "metabase/visualizations/lib/CardRenderer";
import { setLatitudeAndLongitude } from "metabase/lib/visualization_settings";
import { isSameSeries } from "metabase/visualizations/lib/utils";
import { getSettingsForVisualization } from "metabase/lib/visualization_settings";
@ExplicitSize
......@@ -53,29 +51,7 @@ export default class CardRenderer extends Component {
}
}));
if (this.props.chartType === "pin_map") {
// call signature is (elementId, card, updateMapCenter (callback), updateMapZoom (callback))
// identify the lat/lon columns from our data and make them part of the viz settings so we can render maps
let card = {
...series[0].card,
visualization_settings: setLatitudeAndLongitude(series[0].card.visualization_settings, series[0].data.cols)
}
// these are example callback functions that could be passed into the renderer
var updateMapCenter = (lat, lon) => {
this.props.onUpdateVisualizationSetting(["map", "center_latitude"], lat);
this.props.onUpdateVisualizationSetting(["map", "center_longitude"], lat);
};
var updateMapZoom = (zoom) => {
this.props.onUpdateVisualizationSetting(["map", "zoom"], zoom);
};
charting.CardRenderer[this.props.chartType](element, { ...this.props, card, updateMapCenter, updateMapZoom });
} else {
charting.CardRenderer[this.props.chartType](element, { ...this.props, series, card: series[0].card, data: series[0].data });
}
charting.CardRenderer[this.props.chartType](element, { ...this.props, series, card: series[0].card, data: series[0].data });
}
} catch (err) {
console.error(err);
......
/*global google*/
import _ from "underscore";
import crossfilter from "crossfilter";
import d3 from "d3";
......@@ -733,80 +731,5 @@ export let CardRenderer = {
.render();
return chartRenderer;
},
pin_map(element, { card, updateMapCenter, updateMapZoom }) {
let query = card.dataset_query;
let vs = card.visualization_settings;
let latitude_dataset_col_index = vs.map.latitude_dataset_col_index;
let longitude_dataset_col_index = vs.map.longitude_dataset_col_index;
let latitude_source_table_field_id = vs.map.latitude_source_table_field_id;
let longitude_source_table_field_id = vs.map.longitude_source_table_field_id;
if (latitude_dataset_col_index == null || longitude_dataset_col_index == null) {
return;
}
if (latitude_source_table_field_id == null || longitude_source_table_field_id == null) {
throw ("Map ERROR: latitude and longitude column indices must be specified");
}
if (latitude_dataset_col_index == null || longitude_dataset_col_index == null) {
throw ("Map ERROR: unable to find specified latitude / longitude columns in source table");
}
let mapOptions = {
zoom: vs.map.zoom,
center: new google.maps.LatLng(vs.map.center_latitude, vs.map.center_longitude),
mapTypeId: google.maps.MapTypeId.MAP,
scrollwheel: false
};
let markerImageMapType = new google.maps.ImageMapType({
getTileUrl: (coord, zoom) =>
'/api/tiles/' + zoom + '/' + coord.x + '/' + coord.y + '/' +
latitude_source_table_field_id + '/' + longitude_source_table_field_id + '/' +
latitude_dataset_col_index + '/' + longitude_dataset_col_index + '/' +
'?query=' + encodeURIComponent(JSON.stringify(query))
,
tileSize: new google.maps.Size(256, 256)
});
let height = getAvailableCanvasHeight(element);
if (height != null) {
element.style.height = height + "px";
}
let width = getAvailableCanvasWidth(element);
if (width != null) {
element.style.width = width + "px";
}
let map = new google.maps.Map(element, mapOptions);
map.overlayMapTypes.push(markerImageMapType);
map.addListener("center_changed", () => {
let center = map.getCenter();
updateMapCenter(center.lat(), center.lng());
});
map.addListener("zoom_changed", () => {
updateMapZoom(map.getZoom());
});
/* We need to trigger resize at least once after
* this function (re)configures the map, because if
* a map already existed in this div (i.e. this
* function was called as a result of a settings
* change), then the map will re-render with
* the new options once resize is called.
* Otherwise, the map will not re-render.
*/
google.maps.event.trigger(map, 'resize');
//listen for resize event (internal to CardRenderer)
//to let google maps api know about the resize
//(see https://developers.google.com/maps/documentation/javascript/reference)
element.addEventListener('cardrenderer-card-resized', () => google.maps.event.trigger(map, 'resize'));
}
};
resources/frontend_client/app/img/pin.png

1.49 KiB

(ns metabase.api.tiles
(:require [clojure.core.match :refer [match]]
[clojure.java.io :as io]
[compojure.core :refer [GET]]
[metabase.api.common :refer :all]
[metabase.db :refer :all]
......@@ -14,10 +15,12 @@
(def ^:private ^:const tile-size 256.0)
(def ^:private ^:const pixel-origin (float (/ tile-size 2)))
(def ^:private ^:const pin-size 5)
(def ^:private ^:const pin-size 2)
(def ^:private ^:const pixels-per-lon-degree (float (/ tile-size 360)))
(def ^:private ^:const pixels-per-lon-radian (float (/ tile-size (* 2 Math/PI))))
(def ^:private ^:const pin-filename "frontend_client/app/img/pin.png")
(def ^:private pin-image (ImageIO/read (io/resource pin-filename)))
;;; # ------------------------------------------------------------ UTIL FNS ------------------------------------------------------------
......@@ -79,7 +82,7 @@
tile-pixel {:x (mod (map-pixel :x) tile-size)
:y (mod (map-pixel :y) tile-size)}]
;; now draw a "pin" at the given tile pixel location
(.fillOval graphics (tile-pixel :x) (tile-pixel :y) pin-size pin-size)))
(.drawImage graphics pin-image (- (tile-pixel :x) 14) (- (tile-pixel :y) 24) nil nil)))
(catch Throwable e
(.printStackTrace e))
(finally
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment