supercluster-googlemaps-adapter
Advanced tools
Comparing version 1.0.10 to 1.0.11
@@ -19,2 +19,3 @@ /// <reference types="googlemaps" /> | ||
private pFeatureClick; | ||
private pClusterClick; | ||
private pFeatureStyle; | ||
@@ -38,6 +39,7 @@ private pServerSideFeatureToSuperCluster; | ||
withFeatureClick(featureClick: (event: google.maps.Data.MouseEvent) => void): Builder; | ||
withClusterClick(clusterClick: (marker: google.maps.Marker, event: google.maps.MouseEvent, mapInstance: google.maps.Map) => void): Builder; | ||
withFeatureStyle(featureStyle: google.maps.Data.StylingFunction): Builder; | ||
withServerSideFeatureToSuperCluster(transform: (feature: any) => Supercluster.ClusterFeature<Supercluster.AnyProps> | Supercluster.PointFeature<Supercluster.AnyProps>): Builder; | ||
withOverlapMarkerSpiderfier(oms: OverlappingMarkerSpiderfier): Builder; | ||
withGetClustersServerSide(getClusters: (bbox: GeoJSON.BBox, zoom: number) => Promise<any[]>): Builder; | ||
withGetClustersServerSide(getClusters: (bbox: GeoJSON.BBox, zoom: number, clusterToZoom?: string) => Promise<any[]>): Builder; | ||
build(): SuperClusterAdapter; | ||
@@ -57,2 +59,3 @@ get map(): google.maps.Map; | ||
get featureClick(): (event: google.maps.Data.MouseEvent) => void; | ||
get clusterClick(): ((marker: google.maps.Marker, event: google.maps.MouseEvent, mapInstance: google.maps.Map) => void) | undefined; | ||
get featureStyle(): google.maps.Data.StylingFunction; | ||
@@ -59,0 +62,0 @@ get serverSideFeatureToSuperCluster(): (feature: any) => Supercluster.ClusterFeature<Supercluster.AnyProps> | Supercluster.PointFeature<Supercluster.AnyProps>; |
@@ -26,2 +26,3 @@ /// <reference types="googlemaps" /> | ||
private pFeatureClick; | ||
private pClusterClick; | ||
private pFeatureStyle; | ||
@@ -28,0 +29,0 @@ private pServerSideFeatureToSuperCluster; |
{ | ||
"name": "supercluster-googlemaps-adapter", | ||
"version": "1.0.10", | ||
"version": "1.0.11", | ||
"description": "Supercluster Adapter for Google Maps JavaScript API v3", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
524
README.md
@@ -13,12 +13,18 @@ # Supercluster Adapter for Google Maps JavaScript API v3 | ||
<script src="https://maps-tools-242a6.firebaseapp.com/clusterer/supercluster/index.js"> | ||
</script> | ||
```html | ||
<script src="https://maps-tools-242a6.firebaseapp.com/clusterer/supercluster/index.js"> | ||
</script> | ||
``` | ||
It can also be installed from npm | ||
npm install supercluster-googlemaps-adapter --save | ||
```sh | ||
npm install supercluster-googlemaps-adapter --save | ||
``` | ||
Add library to your javascript or typescript file as | ||
import { SuperClusterAdapterLoader } from 'supercluster-googlemaps-adapter'; | ||
```js | ||
import { SuperClusterAdapterLoader } from 'supercluster-googlemaps-adapter'; | ||
``` | ||
@@ -29,5 +35,7 @@ Please note that this library depends on the Google Maps JavaScript API, so it should be initialized once the Google Maps JavaScript API is fully loaded. | ||
<script async defer | ||
src="https://maps.googleapis.com/maps/api/js?key=[YOUR_API_KEY]]&callback=initMap"> | ||
</script> | ||
```html | ||
<script async defer | ||
src="https://maps.googleapis.com/maps/api/js?key=[YOUR_API_KEY]]&callback=initMap"> | ||
</script> | ||
``` | ||
@@ -38,28 +46,30 @@ That means we must include clusterer initialization code inside initMap() callback function after map object initialization. | ||
function initMap() { | ||
var map = new google.maps.Map(document.getElementById('map'), { | ||
zoom: 5, | ||
center: {lat: 41.3850639, lng: 2.1734035} | ||
}); | ||
```js | ||
function initMap() { | ||
var map = new google.maps.Map(document.getElementById('map'), { | ||
zoom: 5, | ||
center: {lat: 41.3850639, lng: 2.1734035} | ||
}); | ||
google.maps.event.addListenerOnce(map, 'tilesloaded', () => { | ||
SuperClusterAdapterLoader.getClusterer().then(Clusterer => { | ||
if (Clusterer) { | ||
const clusterer = new Clusterer.Builder(map) | ||
.withRadius(80) | ||
.withMaxZoom(19) | ||
.withMinZoom(0) | ||
.build(); | ||
google.maps.event.addListenerOnce(map, 'tilesloaded', () => { | ||
SuperClusterAdapterLoader.getClusterer().then(Clusterer => { | ||
if (Clusterer) { | ||
const clusterer = new Clusterer.Builder(map) | ||
.withRadius(80) | ||
.withMaxZoom(19) | ||
.withMinZoom(0) | ||
.build(); | ||
fetch(URL_TO_GET_GEOJSON_DATA).then(response => { | ||
return response.json(); | ||
}).then(data => { | ||
clusterer.load(data); | ||
}).catch(err => { | ||
console.log("Cannot fetch GeoJSON data for this example"); | ||
}); | ||
} | ||
fetch(URL_TO_GET_GEOJSON_DATA).then(response => { | ||
return response.json(); | ||
}).then(data => { | ||
clusterer.load(data); | ||
}).catch(err => { | ||
console.log("Cannot fetch GeoJSON data for this example"); | ||
}); | ||
}); | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
``` | ||
@@ -76,7 +86,9 @@ In this example the clusterer iniatialization was carried out as a response to `tilesloaded` event of map instance. This event is triggered when all tiles of Google Maps are loaded and map is ready to be used. | ||
SuperClusterAdapterLoader.getClusterer().then(Clusterer => { | ||
if (Clusterer) { | ||
// TODO: create instance of clusterer | ||
} | ||
}); | ||
```js | ||
SuperClusterAdapterLoader.getClusterer().then(Clusterer => { | ||
if (Clusterer) { | ||
// TODO: create instance of clusterer | ||
} | ||
}); | ||
``` | ||
@@ -87,7 +99,9 @@ ### Create instance of clusterer | ||
const clusterer = new Clusterer.Builder(map) | ||
.withRadius(80) | ||
.withMaxZoom(19) | ||
.withMinZoom(0) | ||
.build(); | ||
```js | ||
const clusterer = new Clusterer.Builder(map) | ||
.withRadius(80) | ||
.withMaxZoom(19) | ||
.withMinZoom(0) | ||
.build(); | ||
``` | ||
@@ -126,12 +140,14 @@ ### Builder | ||
interface IStyle { | ||
url: string; // Url of icon to use | ||
height: number; // Height of icon | ||
width: number; // Width of icon | ||
textColor?: string; // Text color of cluster label | ||
fontFamily?: string; // Font family of cluster label | ||
textSize?: number; // Text size of cluster label | ||
fontWeight?: string; // Font weight of cluster label | ||
anchor?: number[] | null; // Anchor of cluster icon | ||
} | ||
```ts | ||
interface IStyle { | ||
url: string; // Url of icon to use | ||
height: number; // Height of icon | ||
width: number; // Width of icon | ||
textColor?: string; // Text color of cluster label | ||
fontFamily?: string; // Font family of cluster label | ||
textSize?: number; // Text size of cluster label | ||
fontWeight?: string; // Font weight of cluster label | ||
anchor?: number[] | null; // Anchor of cluster icon | ||
} | ||
``` | ||
@@ -156,13 +172,15 @@ #### withImagePath(imagePath: string) | ||
function customMarkerIcon(feature) { | ||
if (feature.properties.iconUrl) { | ||
return feature.properties.iconUrl; | ||
} else { | ||
return "http://maps.google.com/mapfiles/kml/paddle/pink-blank.png"; | ||
} | ||
} | ||
```js | ||
function customMarkerIcon(feature) { | ||
if (feature.properties.iconUrl) { | ||
return feature.properties.iconUrl; | ||
} else { | ||
return "http://maps.google.com/mapfiles/kml/paddle/pink-blank.png"; | ||
} | ||
} | ||
const clusterer = new Clusterer.Builder(map) | ||
.withCustomMarkerIcon(customMarkerIcon) | ||
.build(); | ||
const clusterer = new Clusterer.Builder(map) | ||
.withCustomMarkerIcon(customMarkerIcon) | ||
.build(); | ||
``` | ||
@@ -183,43 +201,45 @@ #### withCustomClusterIcon(customIcon: (clusterFeature: Supercluster.ClusterFeature<Supercluster.AnyProps>, clusterIndex: number) => google.maps.Icon | google.maps.Symbol | null) | ||
function customClusterIcon(feature, index) { | ||
let color; | ||
let scale; | ||
switch (index) { | ||
case 1: | ||
color = YELLOW; | ||
scale = SCALE.small; | ||
break; | ||
case 2: | ||
color = GREEN; | ||
scale = SCALE.medium; | ||
break; | ||
case 3: | ||
color = BLUE; | ||
scale = SCALE.big; | ||
break; | ||
case 4: | ||
color = RED; | ||
scale = SCALE.huge; | ||
break; | ||
default: | ||
color = GREY; | ||
scale = SCALE.small; | ||
break; | ||
} | ||
return { | ||
path: CLUSTER_PATH, | ||
fillColor: color.fillColor, | ||
fillOpacity: 1, | ||
strokeColor: color.strokeColor, | ||
strokeOpacity: 1, | ||
strokeWeight: 13, | ||
scale: scale, | ||
anchor: new google.maps.Point(70, 70), | ||
labelOrigin: new google.maps.Point(74, 74) | ||
}; | ||
} | ||
```js | ||
function customClusterIcon(feature, index) { | ||
let color; | ||
let scale; | ||
switch (index) { | ||
case 1: | ||
color = YELLOW; | ||
scale = SCALE.small; | ||
break; | ||
case 2: | ||
color = GREEN; | ||
scale = SCALE.medium; | ||
break; | ||
case 3: | ||
color = BLUE; | ||
scale = SCALE.big; | ||
break; | ||
case 4: | ||
color = RED; | ||
scale = SCALE.huge; | ||
break; | ||
default: | ||
color = GREY; | ||
scale = SCALE.small; | ||
break; | ||
} | ||
return { | ||
path: CLUSTER_PATH, | ||
fillColor: color.fillColor, | ||
fillOpacity: 1, | ||
strokeColor: color.strokeColor, | ||
strokeOpacity: 1, | ||
strokeWeight: 13, | ||
scale: scale, | ||
anchor: new google.maps.Point(70, 70), | ||
labelOrigin: new google.maps.Point(74, 74) | ||
}; | ||
} | ||
const clusterer = new Clusterer.Builder(map) | ||
.withCustomClusterIcon(customClusterIcon) | ||
.build(); | ||
const clusterer = new Clusterer.Builder(map) | ||
.withCustomClusterIcon(customClusterIcon) | ||
.build(); | ||
``` | ||
@@ -232,26 +252,28 @@ #### withUpdateMarkerOptions(updateMarkerOptions: (scfeature: Supercluster.PointFeature<Supercluster.AnyProps> | Supercluster.ClusterFeature<Supercluster.AnyProps>, marker: google.maps.Marker) => google.maps.MarkerOptions | null) | ||
function updateMarkerOptions(feature, marker) { | ||
function randomColor() { | ||
var arr = [GREEN, RED, BLUE, YELLOW, BROWN, GREY]; | ||
var rand = Math.floor(Math.random()*(arr.length-1)); | ||
return arr[rand]; | ||
} | ||
```js | ||
function updateMarkerOptions(feature, marker) { | ||
function randomColor() { | ||
var arr = [GREEN, RED, BLUE, YELLOW, BROWN, GREY]; | ||
var rand = Math.floor(Math.random()*(arr.length-1)); | ||
return arr[rand]; | ||
} | ||
var color = randomColor(); | ||
var markerIcon = marker.getIcon(); | ||
var color = randomColor(); | ||
var markerIcon = marker.getIcon(); | ||
if (feature.properties.cluster === true) { | ||
markerIcon.fillColor = color.fillColor; | ||
markerIcon.strokeColor = color.strokeColor; | ||
} else { | ||
markerIcon.fillColor = color.fillColor; | ||
} | ||
return { | ||
icon: markerIcon | ||
}; | ||
} | ||
if (feature.properties.cluster === true) { | ||
markerIcon.fillColor = color.fillColor; | ||
markerIcon.strokeColor = color.strokeColor; | ||
} else { | ||
markerIcon.fillColor = color.fillColor; | ||
} | ||
return { | ||
icon: markerIcon | ||
}; | ||
} | ||
const clusterer = new Clusterer.Builder(map) | ||
.withUpdateMarkerOptions(updateMarkerOptions) | ||
.build(); | ||
const clusterer = new Clusterer.Builder(map) | ||
.withUpdateMarkerOptions(updateMarkerOptions) | ||
.build(); | ||
``` | ||
@@ -264,13 +286,15 @@ #### withMarkerClick(markerClick: (marker: google.maps.Marker, event: google.maps.MouseEvent) => void) | ||
function onMarkerClick(marker, event) { | ||
infoWindow.close(); | ||
var title = marker.getTitle(); | ||
var content = `<h2>${title}</h2>`; | ||
infoWindow.setContent(content); | ||
infoWindow.open(map, marker); | ||
} | ||
```js | ||
function onMarkerClick(marker, event) { | ||
infoWindow.close(); | ||
var title = marker.getTitle(); | ||
var content = `<h2>${title}</h2>`; | ||
infoWindow.setContent(content); | ||
infoWindow.open(map, marker); | ||
} | ||
const clusterer = new Clusterer.Builder(map) | ||
.withMarkerClick(onMarkerClick) | ||
.build(); | ||
const clusterer = new Clusterer.Builder(map) | ||
.withMarkerClick(onMarkerClick) | ||
.build(); | ||
``` | ||
@@ -283,20 +307,43 @@ #### withFeatureClick(featureClick: (event: google.maps.Data.MouseEvent) => void) | ||
function onFeatureClick(event) { | ||
infoWindow.close(); | ||
if (event.feature) { | ||
var title = event.feature.getProperty("name"); | ||
var content = `<h2>${title}</h2>`; | ||
infoWindow.setOptions({ | ||
content: content, | ||
position: event.latLng, | ||
map: map | ||
}); | ||
infoWindow.open(map); | ||
} | ||
} | ||
```js | ||
function onFeatureClick(event) { | ||
infoWindow.close(); | ||
if (event.feature) { | ||
var title = event.feature.getProperty("name"); | ||
var content = `<h2>${title}</h2>`; | ||
infoWindow.setOptions({ | ||
content: content, | ||
position: event.latLng, | ||
map: map | ||
}); | ||
infoWindow.open(map); | ||
} | ||
} | ||
const clusterer = new Clusterer.Builder(map) | ||
.withFeatureClick(onFeatureClick) | ||
.build(); | ||
const clusterer = new Clusterer.Builder(map) | ||
.withFeatureClick(onFeatureClick) | ||
.build(); | ||
``` | ||
#### withClusterClick(clusterClick: (marker: google.maps.Marker, event: google.maps.MouseEvent, mapInstance: google.maps.Map) => void) | ||
You can define a callback function to respond the click events on cluster markers. This function receives three parameters. The first is the Google Maps [Marker][marker], the second is Google Maps [MouseEvent][mouseevent] and the third is Google [Map][map] instance. It might be useful to implerments logic that splits the cluster and zooms the map. | ||
E.g. | ||
```js | ||
function onClusterClick(marker, event, map) { | ||
const clusterId = marker.get('cluster_id'); | ||
const zoom = getClusterExpansionZoom(clusterId); | ||
map.setOptions({ | ||
center: event.latLng, | ||
zoom, | ||
}); | ||
} | ||
const clusterer = new Clusterer.Builder(map) | ||
.withClusterClick(onClusterClick) | ||
.build(); | ||
``` | ||
#### withFeatureStyle(featureStyle: google.maps.Data.StylingFunction) | ||
@@ -308,16 +355,18 @@ | ||
function featureStyle(feature) { | ||
var options = { | ||
fillColor: feature.getProperty("color"), | ||
fillOpacity: 0.5, | ||
strokeColor: feature.getProperty("color"), | ||
strokeOpacity: 1, | ||
strokeWeight: 2 | ||
}; | ||
return options; | ||
} | ||
```js | ||
function featureStyle(feature) { | ||
var options = { | ||
fillColor: feature.getProperty("color"), | ||
fillOpacity: 0.5, | ||
strokeColor: feature.getProperty("color"), | ||
strokeOpacity: 1, | ||
strokeWeight: 2 | ||
}; | ||
return options; | ||
} | ||
const clusterer = new Clusterer.Builder(map) | ||
.withFeatureStyle(featureStyle) | ||
.build(); | ||
const clusterer = new Clusterer.Builder(map) | ||
.withFeatureStyle(featureStyle) | ||
.build(); | ||
``` | ||
@@ -330,27 +379,29 @@ #### withServerSideFeatureToSuperCluster(transform: (feature: any) => Supercluster.ClusterFeature<Supercluster.AnyProps> | Supercluster.PointFeature<Supercluster.AnyProps>) | ||
function itemToSuperclusterFeature(item) { | ||
var res = { | ||
type: "Feature", | ||
geometry: { | ||
type: "Point", | ||
coordinates: [item.point.longitude, item.point.latitude] | ||
}, | ||
properties: { | ||
} | ||
}; | ||
if (item.positionsInside > 1) { // Cluster | ||
res.properties.cluster = true; | ||
res.properties.cluster_id = getNewId(); | ||
res.properties.point_count = item.positionsInside; | ||
res.properties.point_count_abbreviated = abbreviateNumber(item.positionsInside); | ||
} else { // Point | ||
res.properties.id = item.assetMapPosition.id; | ||
res.properties.name = item.assetMapPosition.assetName; | ||
} | ||
return res; | ||
```js | ||
function itemToSuperclusterFeature(item) { | ||
let res = { | ||
type: "Feature", | ||
geometry: { | ||
type: "Point", | ||
coordinates: [item.point.longitude, item.point.latitude] | ||
}, | ||
properties: { | ||
} | ||
}; | ||
if (item.positionsInside > 1) { // Cluster | ||
res.properties.cluster = true; | ||
res.properties.cluster_id = getNewId(); | ||
res.properties.point_count = item.positionsInside; | ||
res.properties.point_count_abbreviated = abbreviateNumber(item.positionsInside); | ||
} else { // Point | ||
res.properties.id = item.assetMapPosition.id; | ||
res.properties.name = item.assetMapPosition.assetName; | ||
} | ||
return res; | ||
} | ||
const clusterer = new Clusterer.Builder(map) | ||
.withServerSideFeatureToSuperCluster(itemToSuperclusterFeature) | ||
.build(); | ||
const clusterer = new Clusterer.Builder(map) | ||
.withServerSideFeatureToSuperCluster(itemToSuperclusterFeature) | ||
.build(); | ||
``` | ||
@@ -369,23 +420,25 @@ Please note that for clusters you have to define mandatory properties `cluster, cluster_id, point_count, point_count_abbreviated`. The feature that you return is a GeoJSON [Feature][feature] with [Point][point] geometry. | ||
const clusterer = new Clusterer.Builder(map) | ||
.withServerSideFeatureToSuperCluster(itemToSuperclusterFeature) | ||
.withGetClustersServerSide(async (bbox, zoom) => { | ||
let features; | ||
const query = `{"bounds":{"west":${bbox[0]},"south":${bbox[1]},"east":${bbox[2]},"north":${bbox[3]}}, "zoom":${zoom}}`; | ||
try { | ||
const response = await fetch(url + encodeURIComponent(query)); | ||
if (response.ok) { | ||
const featureCollection = await response.json(); | ||
features = featureCollection.features; | ||
} else { | ||
console.log(`Cannot fetch server side clusters data for this example ${response.statusText}`); | ||
features = []; | ||
} | ||
} catch (err) { | ||
console.log(`Cannot fetch server side clusters data for this example ${err}`); | ||
features = []; | ||
} | ||
return features; | ||
}) | ||
.build(); | ||
```js | ||
const clusterer = new Clusterer.Builder(map) | ||
.withServerSideFeatureToSuperCluster(itemToSuperclusterFeature) | ||
.withGetClustersServerSide(async (bbox, zoom) => { | ||
let features; | ||
const query = `{"bounds":{"west":${bbox[0]},"south":${bbox[1]},"east":${bbox[2]},"north":${bbox[3]}}, "zoom":${zoom}}`; | ||
try { | ||
const response = await fetch(url + encodeURIComponent(query)); | ||
if (response.ok) { | ||
const featureCollection = await response.json(); | ||
features = featureCollection.features; | ||
} else { | ||
console.log(`Cannot fetch server side clusters data for this example ${response.statusText}`); | ||
features = []; | ||
} | ||
} catch (err) { | ||
console.log(`Cannot fetch server side clusters data for this example ${err}`); | ||
features = []; | ||
} | ||
return features; | ||
}) | ||
.build(); | ||
``` | ||
@@ -400,19 +453,25 @@ Please note that if you defined the callback function using `withGetClustersServerSide()` there is no need to call `drawServerSideCalculatedClusters(features: any[])` anymore. It will be called automatically. | ||
`<script src="https://cdnjs.cloudflare.com/ajax/libs/OverlappingMarkerSpiderfier/1.0.3/oms.min.js"></script>` | ||
```html | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/OverlappingMarkerSpiderfier/1.0.3/oms.min.js"></script> | ||
``` | ||
Initialize the instance of spiderfier once you have initialized a map instance. E.g. | ||
var oms = new OverlappingMarkerSpiderfier(map, { | ||
markersWontMove: true, | ||
markersWontHide: true, | ||
basicFormatEvents: true, | ||
ignoreMapClick: true, | ||
keepSpiderfied: true | ||
}); | ||
```js | ||
const oms = new OverlappingMarkerSpiderfier(map, { | ||
markersWontMove: true, | ||
markersWontHide: true, | ||
basicFormatEvents: true, | ||
ignoreMapClick: true, | ||
keepSpiderfied: true | ||
}); | ||
``` | ||
Pass instance of spidefier to the supercluster adapter builder: | ||
const clusterer = new Clusterer.Builder(map) | ||
.withOverlapMarkerSpiderfier(oms) | ||
.build(); | ||
```js | ||
const clusterer = new Clusterer.Builder(map) | ||
.withOverlapMarkerSpiderfier(oms) | ||
.build(); | ||
``` | ||
@@ -429,9 +488,11 @@ Enjoy. | ||
fetch(URL_TO_GET_GEOJSON_DATA).then(response => { | ||
return response.json(); | ||
}).then(data => { | ||
clusterer.load(data); | ||
}).catch(err => { | ||
console.log("Cannot fetch GeoJSON data for this example"); | ||
}); | ||
```js | ||
fetch(URL_TO_GET_GEOJSON_DATA).then(response => { | ||
return response.json(); | ||
}).then(data => { | ||
clusterer.load(data); | ||
}).catch(err => { | ||
console.log("Cannot fetch GeoJSON data for this example"); | ||
}); | ||
``` | ||
@@ -444,10 +505,12 @@ In order to display clusters calculated by your endpoint on server-side you should call method | ||
fetch(URL_TO_GET_SERVERSIDE_DATA).then(response => { | ||
return response.text(); | ||
}).then(data => { | ||
var jsonData = JSON.parse(data); | ||
clusterer.drawServerSideCalculatedClusters(jsonData.mapPositions); | ||
}).catch(err => { | ||
console.log("Cannot fetch data for this example"); | ||
}); | ||
```js | ||
fetch(URL_TO_GET_SERVERSIDE_DATA).then(response => { | ||
return response.text(); | ||
}).then(data => { | ||
var jsonData = JSON.parse(data); | ||
clusterer.drawServerSideCalculatedClusters(jsonData.mapPositions); | ||
}).catch(err => { | ||
console.log("Cannot fetch data for this example"); | ||
}); | ||
``` | ||
@@ -515,1 +578,2 @@ Do not use this method if you defined the `withGetClustersServerSide()` callback. The typical scenario when you might need the `drawServerSideCalculatedClusters()` is the following: | ||
[markeroptions]: https://developers.google.com/maps/documentation/javascript/reference/marker#MarkerOptions | ||
[map]: https://developers.google.com/maps/documentation/javascript/reference/map#Map |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
325502
485
561