Skip to content

Commit

Permalink
feat!: marker clusters
Browse files Browse the repository at this point in the history
  • Loading branch information
HusamElbashir committed May 11, 2022
1 parent 72d4b4e commit 5d58e2e
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 19 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"release": "standard-version"
},
"dependencies": {
"@googlemaps/js-api-loader": "^1.12.11"
"@googlemaps/js-api-loader": "^1.12.11",
"@googlemaps/markerclusterer": "^2.0.6"
},
"devDependencies": {
"@ampproject/rollup-plugin-closure-compiler": "^0.26.0",
Expand Down
4 changes: 2 additions & 2 deletions src/components/CustomControl.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export default defineComponent({
setup(props, { emit }) {
const controlRef = ref<HTMLElement | null>(null);
const map = inject(mapSymbol, ref(null));
const api = inject(apiSymbol, ref(null));
const map = inject(mapSymbol, ref());
const api = inject(apiSymbol, ref());
const mapTilesLoaded = inject(mapTilesLoadedSymbol, ref(false));
const showContent = ref(false);
Expand Down
6 changes: 3 additions & 3 deletions src/components/InfoWindow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export default defineComponent({
const infoWindow = ref<google.maps.InfoWindow>();
const infoWindowRef = ref<HTMLElement>();
const map = inject(mapSymbol, ref(null));
const api = inject(apiSymbol, ref(null));
const anchor = inject(markerSymbol, ref(null));
const map = inject(mapSymbol, ref());
const api = inject(apiSymbol, ref());
const anchor = inject(markerSymbol, ref());
let anchorClickListener: google.maps.MapsEventListener;
const hasSlotContent = computed(() => slots.default?.().some((vnode) => vnode.type !== Comment));
Expand Down
51 changes: 51 additions & 0 deletions src/components/MarkerCluster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { defineComponent, PropType, ref, provide, inject, watch, markRaw, onBeforeUnmount } from "vue";
import { MarkerClusterer, MarkerClustererOptions } from "@googlemaps/markerclusterer";
import { mapSymbol, apiSymbol, markerClusterSymbol } from "../shared/index";

const markerClusterEvents = ["clusteringbegin", "clusteringend", "click"];

export default defineComponent({
name: "MarkerCluster",
props: {
options: {
type: Object as PropType<MarkerClustererOptions>,
default: () => ({}),
},
},
emits: markerClusterEvents,
setup(props, { emit, expose, slots }) {
const markerCluster = ref<MarkerClusterer>();
const map = inject(mapSymbol, ref());
const api = inject(apiSymbol, ref());

provide(markerClusterSymbol, markerCluster);

watch(
map,
() => {
if (map.value) {
markerCluster.value = markRaw(new MarkerClusterer({ map: map.value, ...props.options }));

markerClusterEvents.forEach((event) => {
markerCluster.value?.addListener(event, (e: unknown) => emit(event, e));
});
}
},
{
immediate: true,
}
);

onBeforeUnmount(() => {
if (markerCluster.value) {
api.value?.event.clearInstanceListeners(markerCluster.value);
markerCluster.value.clearMarkers();
markerCluster.value.setMap(null);
}
});

expose({ markerCluster });

return () => slots.default?.();
},
});
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { default as Rectangle } from "./Rectangle";
export { default as Circle } from "./Circle";
export { default as CustomControl } from "./CustomControl.vue";
export { default as InfoWindow } from "./InfoWindow.vue";
export { default as MarkerCluster } from "./MarkerCluster";
45 changes: 35 additions & 10 deletions src/composables/useSetupMapComponent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { watch, ref, Ref, inject, onBeforeUnmount } from "vue";
import { apiSymbol, mapSymbol } from "../shared/index";
import { watch, ref, Ref, inject, onBeforeUnmount, computed } from "vue";
import { apiSymbol, mapSymbol, markerClusterSymbol } from "../shared/index";

export type IComponent =
| google.maps.Marker
Expand All @@ -24,8 +24,13 @@ export const useSetupMapComponent = (
let _component: IComponent | null = null;
const component = ref<IComponent | null>(null);

const map = inject(mapSymbol, ref(null));
const api = inject(apiSymbol, ref(null));
const map = inject(mapSymbol, ref());
const api = inject(apiSymbol, ref());
const markerCluster = inject(markerClusterSymbol, ref());

const isMarkerInCluster = computed(
() => !!(markerCluster.value && api.value && _component instanceof api.value.Marker)
);

watch(
[map, options],
Expand All @@ -35,12 +40,27 @@ export const useSetupMapComponent = (
if (_component) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_component.setOptions(options.value as any);
_component.setMap(map.value);

if (isMarkerInCluster.value) {
markerCluster.value?.removeMarker(_component as google.maps.Marker);
markerCluster.value?.addMarker(_component as google.maps.Marker);
}
} else {
component.value = _component = new api.value[componentName]({
...options.value,
map: map.value,
});
if (componentName === "Marker") {
component.value = _component = new api.value[componentName](options.value);
} else {
component.value = _component = new api.value[componentName]({
...options.value,
map: map.value,
});
}

if (isMarkerInCluster.value) {
markerCluster.value?.addMarker(_component as google.maps.Marker);
} else {
_component.setMap(map.value);
}

events.forEach((event) => {
_component?.addListener(event, (e: unknown) => emit(event, e));
});
Expand All @@ -55,7 +75,12 @@ export const useSetupMapComponent = (
onBeforeUnmount(() => {
if (_component) {
api.value?.event.clearInstanceListeners(_component);
_component.setMap(null);

if (isMarkerInCluster.value) {
markerCluster.value?.removeMarker(_component as google.maps.Marker);
} else {
_component.setMap(null);
}
}
});

Expand Down
8 changes: 5 additions & 3 deletions src/shared/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { Loader } from "@googlemaps/js-api-loader";
import type { MarkerClusterer } from "@googlemaps/markerclusterer";
import { InjectionKey, ref, Ref } from "vue";

export const mapSymbol: InjectionKey<Ref<google.maps.Map | null>> = Symbol("map");
export const apiSymbol: InjectionKey<Ref<typeof google.maps | null>> = Symbol("api");
export const markerSymbol: InjectionKey<Ref<google.maps.Marker | null>> = Symbol("marker");
export const mapSymbol: InjectionKey<Ref<google.maps.Map>> = Symbol("map");
export const apiSymbol: InjectionKey<Ref<typeof google.maps>> = Symbol("api");
export const markerSymbol: InjectionKey<Ref<google.maps.Marker>> = Symbol("marker");
export const markerClusterSymbol: InjectionKey<Ref<MarkerClusterer>> = Symbol("markerCluster");
/**
* Utilitary flag for components that need to know the map
* was fully loaded (including its tiles) to decide their behavior
Expand Down
20 changes: 20 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,14 @@
dependencies:
fast-deep-equal "^3.1.3"

"@googlemaps/markerclusterer@^2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@googlemaps/markerclusterer/-/markerclusterer-2.0.6.tgz#f3c157e24f5c95ab1748710c5a584ae1fae51d61"
integrity sha512-kO8Q77V3aqR2tVZ3SDXs9ycCiWYpd+FadxIJVtDKlO9LlMs415GS686+XvDLMLorR/RvwQHkquHZM8RbZ3bCrg==
dependencies:
fast-deep-equal "^3.1.3"
supercluster "^7.1.3"

"@hutson/parse-repository-url@^3.0.0":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340"
Expand Down Expand Up @@ -3955,6 +3963,11 @@ jsonparse@^1.2.0:
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=

kdbush@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0"
integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==

kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
Expand Down Expand Up @@ -6383,6 +6396,13 @@ stylehacks@^4.0.0:
postcss "^7.0.0"
postcss-selector-parser "^3.0.0"

supercluster@^7.1.3:
version "7.1.5"
resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.5.tgz#65a6ce4a037a972767740614c19051b64b8be5a3"
integrity sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==
dependencies:
kdbush "^3.0.0"

supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
Expand Down

0 comments on commit 5d58e2e

Please sign in to comment.