import { HTMLWidget } from "@hpcc-js/common";
import { AbsoluteSurface } from "@hpcc-js/layout";
import { map as d3Map } from "d3-collection";
import * as _GoogleMapsLoader from "google-maps";
const GoogleMapsLoader = _GoogleMapsLoader.default || _GoogleMapsLoader;
import "../src/GMap.css";
declare const window: any;
export let google: any = null;
let _googleMapPromise;
export function requireGoogleMap() {
if (!_googleMapPromise) {
_googleMapPromise = new Promise(function (resolve, reject) {
if (google) {
resolve();
}
if (!window.__hpcc_gmap_apikey) {
console.warn("__hpcc_gmap_apikey does not contain a valid API key, reverting to developers key (expect limited performance)");
}
GoogleMapsLoader.KEY = window.__hpcc_gmap_apikey || "AIzaSyDwGn2i1i_pMZvnqYJN1BksD_tjYaCOWKg";
GoogleMapsLoader.LIBRARIES = ["geometry", "drawing"];
GoogleMapsLoader.load(function (_google) {
google = _google;
resolve();
});
});
}
return _googleMapPromise;
}
function createOverlay(map, worldSurface, viewportSurface) {
function Overlay(map2, worldSurface2, viewportSurface2) {
google.maps.OverlayView.call(this);
this._div = null;
this._worldSurface = worldSurface2;
this._viewportSurface = viewportSurface2;
this._map = map2;
this.setMap(map2);
const context = this;
google.maps.event.addListener(map2, "bounds_changed", function () {
context.draw();
});
google.maps.event.addListener(map2, "projection_changed", function () {
context.draw();
});
this._prevWorldMin = { x: 0, y: 0 };
this._prevWorldMax = { x: 0, y: 0 };
this._prevMin = { x: 0, y: 0 };
this._prevMax = { x: 0, y: 0 };
}
Overlay.prototype = google.maps.OverlayView.prototype;
Overlay.prototype.onAdd = function () {
this.div = document.createElement("div");
this._viewportSurface.target(null)
.target(this.div)
.units("pixels");
const panes = this.getPanes();
panes.overlayMouseTarget.appendChild(this.div);
};
Overlay.prototype.draw = function () {
const projection = this.getProjection();
if (!projection)
return;
const bounds = this._map.getBounds();
const center = projection.fromLatLngToDivPixel(bounds.getCenter());
const sw = projection.fromLatLngToDivPixel(bounds.getSouthWest());
const ne = projection.fromLatLngToDivPixel(bounds.getNorthEast());
const min = {
x: sw.x,
y: ne.y
};
const max = {
x: ne.x,
y: sw.y
};
const worldWidth = projection.getWorldWidth();
while (max.x < min.x + 100) { // Ignoe dateline from being the rect.
max.x += worldWidth;
}
while (min.x > center.x) {
min.x -= worldWidth;
max.x -= worldWidth;
}
if (min.x !== this._prevMin.x || min.y !== this._prevMin.y || max.x !== this._prevMax.x || max.y !== this._prevMax.y) {
this._viewportSurface
.resize({ width: 0, height: 0 })
.widgetX(min.x)
.widgetY(min.y)
.widgetWidth(max.x - min.x)
.widgetHeight(max.y - min.y)
;
// FF Issue on initial render (GH-1855) ---
if (this._viewportSurface._renderCount) {
this._viewportSurface.render();
this._prevMin = min;
this._prevMax = max;
} else {
this._viewportSurface.lazyRender();
}
}
const worldMin = projection.fromLatLngToDivPixel(new google.maps.LatLng(85, -179.9));
const worldMax = projection.fromLatLngToDivPixel(new google.maps.LatLng(-85, 179.9));
while (worldMax.x < worldMin.x + 100) { // Ignoe dateline from being the rect.
worldMax.x += worldWidth;
}
while (worldMin.x > center.x) {
worldMin.x -= worldWidth;
worldMax.x -= worldWidth;
}
if (worldMin.x !== this._prevWorldMin.x || worldMin.y !== this._prevWorldMin.y || worldMax.x !== this._prevWorldMax.x || worldMax.y !== this._prevWorldMax.y) {
this._worldSurface
.widgetX(worldMin.x)
.widgetY(worldMin.y)
.widgetWidth(worldMax.x - worldMin.x)
.widgetHeight(worldMax.y - worldMin.y)
.render()
;
this._prevWorldMin = worldMax;
this._prevWorldMax = worldMax;
}
};
Overlay.prototype.onRemove = function () {
this._viewportSurface.target(null);
this._div.parentNode.removeChild(this._div);
this._div = null;
};
return new Overlay(map, worldSurface, viewportSurface);
}
class UserShapeSelectionBag {
_userShapes: any[];
mapContext;
constructor(mapObj) {
this._userShapes = [];
this.mapContext = mapObj;
}
add(_) {
const idx = this._userShapes.indexOf(_);
if (idx >= 0) {
return;
}
this._userShapes.push(_);
}
remove(_) {
const idx = this._userShapes.indexOf(_);
if (idx >= 0) {
this._userShapes.splice(idx, 1);
}
_.setMap(null);
}
save() {
return this._userShapes.map(shape => this._saveShape(shape));
}
load(_) {
this._deserializeShapes(_);
}
_saveShape(shape) {
const retVal: any = {};
const createShapes = {
circle: (_) => {
retVal.type = "circle";
retVal.pos = {
lat: _.center.lat(),
lng: _.center.lng()
};
retVal.radius = _.radius;
},
rectangle: (_) => {
retVal.type = "rectangle";
retVal.bounds = {
ne: _.bounds.getNorthEast(),
sw: _.bounds.getSouthWest()
};
},
polygon: (_) => {
retVal.type = _.__hpcc_type;
const vertices = _.getPath();
retVal.vertices = [];
for (let i = 0; i < vertices.length; i++) {
retVal.vertices.push(vertices.getAt(i));
}
},
polyline: (_) => {
createShapes.polygon(_);
}
};
createShapes[shape.__hpcc_type](shape);
retVal.strokeWeight = shape.strokeWeight;
retVal.fillColor = shape.fillColor;
retVal.fillOpacity = shape.fillOpacity;
retVal.editable = shape.editable;
retVal.clickable = shape.clickable || true;
return retVal;
}
_deserializeShapes(_shapes) {
const shapes = JSON.parse(_shapes);
const defOptions = {
strokeWeight: 0,
fillOpacity: 0.45,
fillColor: "#1f77b4",
editable: true,
clickable: true
};
const createShapes = {
circle: (_, map) => {
const shape = new google.maps.Circle({
strokeWeight: _.strokeWeight || defOptions.strokeWeight,
fillColor: _.fillColor || defOptions.fillColor,
fillOpacity: _.fillOpacity || defOptions.fillOpacity,
editable: _.editable || defOptions.editable,
clickable: _.clickable || defOptions.clickable,
map,
center: _.pos,
radius: _.radius
});
return shape;
},
rectangle: (_, map) => {
const shape = new google.maps.Rectangle({
strokeWeight: _.strokeWeight || defOptions.strokeWeight,
fillColor: _.fillColor || defOptions.fillColor,
fillOpacity: _.fillOpacity || defOptions.fillOpacity,
editable: _.editable || defOptions.editable,
clickable: _.clickable || defOptions.clickable,
map,
bounds: {
north: _.bounds.ne.lat,
west: _.bounds.sw.lng,
south: _.bounds.sw.lat,
east: _.bounds.ne.lng
}
});
return shape;
},
polygon: (_, map) => {
const shape = new google.maps.Polygon({
strokeWeight: _.strokeWeight || defOptions.strokeWeight,
fillColor: _.fillColor || defOptions.fillColor,
fillOpacity: _.fillOpacity || defOptions.fillOpacity,
editable: _.editable || defOptions.editable,
clickable: _.clickable || defOptions.clickable,
map,
paths: _.vertices
});
return shape;
},
polyline: (_, map) => {
const shape = new google.maps.Polyline({
strokeWeight: _.strokeWeight || defOptions.strokeWeight,
fillColor: _.fillColor || defOptions.fillColor,
fillOpacity: _.fillOpacity || defOptions.fillOpacity,
editable: _.editable || defOptions.editable,
clickable: _.clickable || defOptions.clickable,
map,
path: _.vertices
});
return shape;
}
};
for (let i = 0; i < shapes.length; i++) {
const shape = createShapes[shapes[i].type](shapes[i], this.mapContext._googleMap);
this.mapContext.onDrawingComplete({ type: shapes[i].type, overlay: shape });
}
}
}
export class GMap extends HTMLWidget {
_overlay;
_userShapes;
_worldSurface;
_viewportSurface;
_googleMapNode;
_googleMap;
_googleGeocoder;
_prevCenterLat;
_prevCenterLong;
_googleStreetViewService;
_googleMapPanorama;
_prevZoom;
_prevStreetView;
_circleMap;
_pinMap;
_drawingManager;
_prevCenterAddress;
_userShapeSelection;
constructor() {
super();
this._tag = "div";
const context = this;
function calcProjection(surface, lat, long) {
const projection = context._overlay.getProjection();
const retVal = projection.fromLatLngToDivPixel(new google.maps.LatLng(lat, long));
const worldWidth = projection.getWorldWidth();
const widgetX = parseFloat(surface.widgetX());
const widgetY = parseFloat(surface.widgetY());
const widgetWidth = parseFloat(surface.widgetWidth());
retVal.x -= widgetX;
retVal.y -= widgetY;
while (retVal.x < 0) {
retVal.x += worldWidth;
}
while (retVal.x > widgetWidth) {
retVal.x -= worldWidth;
}
return retVal;
}
this._userShapes = new UserShapeSelectionBag(this);
this._worldSurface = new AbsoluteSurface();
this._worldSurface.project = function (lat, long) {
return calcProjection(this, lat, long);
};
this._viewportSurface = new AbsoluteSurface();
this._viewportSurface.project = function (lat, long) {
return calcProjection(this, lat, long);
};
}
data(_?) {
const retVal = HTMLWidget.prototype.data.apply(this, arguments);
return retVal;
}
getMapType() {
switch (this.type()) {
case "terrain":
return google.maps.MapTypeId.TERRAIN;
case "road":
return google.maps.MapTypeId.ROADMAP;
case "satellite":
return google.maps.MapTypeId.SATELLITE;
case "hybrid":
return google.maps.MapTypeId.HYBRID;
default:
return google.maps.MapTypeId.ROADMAP;
}
}
getMapOptions() {
return {
panControl: this.panControl(),
zoomControl: this.zoomControl(),
fullscreenControl: this.fullscreenControl(),
mapTypeControl: this.mapTypeControl(),
scaleControl: this.scaleControl(),
streetViewControl: this.streetViewControl(),
overviewMapControl: this.overviewMapControl(),
overviewMapControlOptions: { opened: true },
styles: this.googleMapStyles()
};
}
size(_?) {
const retVal = HTMLWidget.prototype.size.apply(this, arguments);
if (arguments.length && this._googleMapNode) {
this._googleMapNode
.style("width", _.width + "px")
.style("height", _.height + "px")
;
google.maps.event.trigger(this._googleMap, "resize");
}
return retVal;
}
enter(domNode, element) {
super.enter(domNode, element);
const context = this;
this._googleGeocoder = new google.maps.Geocoder();
this._googleMapNode = element.append("div")
.style("width", this.width() + "px")
.style("height", this.height() + "px")
;
this._googleMap = new google.maps.Map(this._googleMapNode.node(), {
zoom: this.zoom(),
center: new google.maps.LatLng(this.centerLat(), this.centerLong()),
mapTypeId: this.getMapType(),
disableDefaultUI: true
});
this._overlay = createOverlay(this._googleMap, this._worldSurface, this._viewportSurface);
this._googleMap.addListener("center_changed", function () {
context.centerLat(context._googleMap.center.lat());
context._prevCenterLat = context.centerLat();
context.centerLong(context._googleMap.center.lng());
context._prevCenterLong = context.centerLong();
context._googleMapPanorama.setPosition({ lat: context.centerLat(), lng: context.centerLong() });
context.zoom(context._googleMap.getZoom());
context._prevZoom = context.zoom();
context._overlay.draw();
});
this._googleMap.addListener("zoom_changed", function () {
context.zoom(context._googleMap.zoom);
context._prevZoom = context.zoom();
});
this._googleStreetViewService = new google.maps.StreetViewService();
this._googleMapPanorama = this._googleMap.getStreetView();
this._googleMapPanorama.addListener("visible_changed", function () {
context.streetView(context._googleMapPanorama.getVisible());
context._prevStreetView = context.streetView();
});
this._circleMap = d3Map([]);
this._pinMap = d3Map([]);
this._prevCenterLat = this.centerLat();
this._prevCenterLong = this.centerLong();
this._prevZoom = this.zoom();
// Init drawing tools with default options.
const defOptions = {
strokeWeight: 0,
fillOpacity: 0.45,
fillColor: "#1f77b4",
editable: true,
clickable: true
};
this._drawingManager = new google.maps.drawing.DrawingManager({
drawingMode: google.maps.drawing.OverlayType.MARKER,
drawingControl: true,
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: ["polygon", "rectangle", "circle"]
},
rectangleOptions: defOptions,
circleOptions: defOptions,
polygonOptions: defOptions
});
if (this.drawingState()) {
this._userShapes.load(this.drawingState());
}
}
update(domNode, element) {
const context = this;
this._googleMapNode
.style("width", this.width() + "px")
.style("height", this.height() + "px")
;
this._googleMap.setMapTypeId(this.getMapType());
this._googleMap.setOptions(this.getMapOptions());
if (this.centerAddress_exists() && this._prevCenterAddress !== this.centerAddress()) {
this._prevCenterAddress = this.centerAddress();
this._googleGeocoder.geocode({ address: this.centerAddress() }, function (results, status) {
if (status === google.maps.GeocoderStatus.OK) {
context._googleMap.fitBounds(results[0].geometry.bounds);
} else {
console.error("Geocode was not successful for the following reason: " + status);
}
});
}
if (this._prevCenterLat !== this.centerLat() || this._prevCenterLong !== this.centerLong()) {
this._googleMap.setCenter(new google.maps.LatLng(this.centerLat(), this.centerLong()));
this._prevCenterLat = this.centerLat();
this._prevCenterLong = this.centerLong();
}
if (this._prevZoom !== this.zoom()) {
this._googleMap.setZoom(this.zoom());
this._prevZoom = this.zoom();
}
this.updateCircles();
this.updatePins();
if (this._prevStreetView !== this.streetView()) {
if (this.streetView()) {
this._googleMapPanorama.setPosition({ lat: this.centerLat(), lng: this.centerLong() });
this._googleMapPanorama.setPov({
heading: 0,
pitch: 0
});
this._googleMapPanorama.setVisible(true);
} else {
this._googleMapPanorama.setVisible(false);
}
this._prevStreetView = this.streetView();
}
// Enable or disable drawing tools.
if (this.drawingTools()) {
this._drawingManager.setMap(this._googleMap);
// Add drawing complete listener to maintain array of drawingState.
google.maps.event.addListener(
this._drawingManager,
"overlaycomplete",
function () {
GMap.prototype.onDrawingComplete.apply(context, arguments);
});
} else {
this._drawingManager.setMap(null);
google.maps.event.clearInstanceListeners(this._drawingManager);
}
}
render(callback?) {
const context = this;
const args = arguments;
requireGoogleMap().then(() => {
super.render.apply(context, args);
});
return this;
}
streetViewAt(pos, radius = 1000) {
const context = this;
this._googleStreetViewService.getPanorama({ location: pos, radius }, function (data, status) {
if (status === "OK") {
const marker = new google.maps.Marker({
position: pos,
map: context._googleMap
});
const heading = google.maps.geometry.spherical.computeHeading(data.location.latLng, new google.maps.LatLng(pos.lat, pos.lng));
context._googleMapPanorama.setPano(data.location.pano);
context._googleMapPanorama.setPov({
heading,
pitch: 0
});
context._googleMapPanorama.setVisible(true);
const listener = google.maps.event.addListener(context._googleMap.getStreetView(), "visible_changed", function () {
if (!this.getVisible()) {
marker.setMap(null);
google.maps.event.removeListener(listener);
}
});
} else {
console.error("Street View data not found for this location.");
}
});
}
updateCircles() {
function rowID(row) {
return row[0] + "_" + row[1];
}
const circle_enter = [];
const circle_update = [];
const circle_exit = d3Map(this._circleMap.keys(), function (d: any) { return d; });
this.data().forEach(function (row) {
circle_exit.remove(rowID(row));
if (row[3] && !this._circleMap.has(rowID(row))) {
circle_enter.push(row);
} else if (row[3] && this._circleMap.has(rowID(row))) {
circle_update.push(row);
} else if (!row[3] && this._circleMap.has(rowID(row))) {
circle_exit.set(rowID(row), true);
}
}, this);
circle_enter.forEach(function (row) {
const marker = this.createCircle(row[0], row[1], row[3]);
this._circleMap.set(rowID(row), marker);
}, this);
circle_update.forEach(function (row) {
// this._pinMap.get(rowID(row)).setIcon(this.createIcon(row[3]));
}, this);
const context = this;
circle_exit.each(function (row) {
context._circleMap.get(row).setMap(null);
context._circleMap.remove(row);
});
}
updatePins() {
function rowID(row) {
return row[0] + "_" + row[1];
}
const pin_enter = [];
const pin_update = [];
const pin_exit = d3Map(this._pinMap.keys(), function (d: any) { return d; });
this.data().forEach(function (row) {
pin_exit.remove(rowID(row));
if (row[2] && !this._pinMap.has(rowID(row))) {
pin_enter.push(row);
} else if (row[2] && this._pinMap.has(rowID(row))) {
pin_update.push(row);
} else if (!row[2] && this._pinMap.has(rowID(row))) {
pin_exit.set(rowID(row), true);
}
}, this);
pin_enter.forEach(function (row) {
const marker = this.createMarker(row[0], row[1], row[2]);
this._pinMap.set(rowID(row), marker);
}, this);
pin_update.forEach(function (row) {
this._pinMap.get(rowID(row)).setIcon(this.createIcon(row[2]));
}, this);
const context = this;
pin_exit.each(function (row) {
context._pinMap.get(row).setMap(null);
context._pinMap.remove(row);
});
}
createIcon(pinObj: { fillColor: string; fillOpacity?: number; strokeColor?: string }) {
return {
path: "M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z M -2,-30", // a 2,2 0 1,1 4,0 2,2 0 1,1",
fillColor: pinObj.fillColor,
fillOpacity: pinObj.fillOpacity || 0.8,
scale: 0.5,
strokeColor: pinObj.strokeColor || "black",
strokeWeight: 0.25
};
}
createMarker(lat, lng, pinObj: { fillColor: string; fillOpacity?: number; strokeColor?: string; title?: string }) {
return new google.maps.Marker({
position: new google.maps.LatLng(lat, lng),
animation: google.maps.Animation.DROP,
title: pinObj.title || "",
icon: this.createIcon(pinObj),
map: this._googleMap
});
}
createCircle(lat, lng, circleObj: { radius?: number; fillColor?: string; strokeColor?: string }) {
circleObj.radius = circleObj.radius || 1;
return new google.maps.Circle({
center: new google.maps.LatLng(lat, lng),
radius: 16093 * circleObj.radius / 10, // 16093 === 10 miles in metres
fillColor: circleObj.fillColor || "red",
strokeColor: circleObj.strokeColor || circleObj.fillColor || "black",
strokeWeight: 0.5,
map: this._googleMap
});
}
zoomTo(selection, singleMaxZoom?) {
if (!this._renderCount) return this;
singleMaxZoom = singleMaxZoom || this.singleZoomToMaxZoom();
let foundCount = 0;
const latlngbounds = new google.maps.LatLngBounds();
selection.forEach(function (item) {
const gLatLong = new google.maps.LatLng(item[0], item[1]);
latlngbounds.extend(gLatLong);
++foundCount;
});
switch (foundCount) {
case 0:
break;
case 1:
this._googleMap.setCenter(latlngbounds.getCenter());
this._googleMap.setZoom(singleMaxZoom);
break;
default:
this._googleMap.fitBounds(latlngbounds);
}
return this;
}
zoomToFit() {
return this.zoomTo(this.data());
}
drawingOptions(_) {
if (!arguments.length) {
return this._drawingManager;
}
this._drawingManager.setOptions(_);
return this;
}
userShapeSelection(_) {
if (!arguments.length) return this._userShapeSelection;
if (this._userShapeSelection) {
this._userShapeSelection.setEditable(false);
}
this._userShapeSelection = _;
if (this._userShapeSelection) {
this._userShapeSelection.setEditable(true);
}
return this;
}
deleteUserShape(_) {
if (this._userShapeSelection === _) {
this.userShapeSelection(null);
}
this._userShapes.remove(_);
}
onDrawingComplete(event) {
if (event.type !== google.maps.drawing.OverlayType.MARKER) {
this._drawingManager.setDrawingMode(null);
const newShape = event.overlay;
newShape.__hpcc_type = event.type;
this._userShapes.add(newShape);
const context = this;
let ctrl = false;
window.addEventListener("keydown", function (e: any) {
if (e.keyIdentifier === "Control" || e.ctrlKey === true) {
ctrl = true;
}
});
window.addEventListener("keyup", function (e) {
if (e.ctrlKey === false) {
ctrl = false;
}
});
google.maps.event.addListener(newShape, "click", function (ev) {
context.userShapeSelection(newShape);
if (ev && ctrl === true) {
context.deleteUserShape(newShape);
context.drawingState(
JSON.stringify(context._userShapes.save()));
}
return false;
});
this.userShapeSelection(newShape);
this.drawingState(
JSON.stringify(this._userShapes.save()));
}
}
}
GMap.prototype._class += " map_GMap";
export interface GMap {
type(): string;
type(_: string): this;
type_exists(): boolean;
centerLat(): number;
centerLat(_: number);
centerLat_exists(): boolean;
centerLong(): number;
centerLong(_: number): this;
centerLong_exists(): boolean;
centerAddress(): string;
centerAddress(_: string): this;
centerAddress_exists(): boolean;
zoom(): number;
zoom(_: number): this;
zoom_exists(): boolean;
singleZoomToMaxZoom(): number;
singleZoomToMaxZoom(_: number): this;
panControl(): boolean;
panControl(_: boolean): this;
panControl_exists(): boolean;
zoomControl(): boolean;
zoomControl(_: boolean): this;
zoomControl_exists(): boolean;
scaleControl(): boolean;
scaleControl(_: boolean): this;
scaleControl_exists(): boolean;
mapTypeControl(): boolean;
mapTypeControl(_: boolean): this;
mapTypeControl_exists(): boolean;
fullscreenControl(): boolean;
fullscreenControl(_: boolean): this;
fullscreenControl_exists(): boolean;
streetViewControl(): boolean;
streetViewControl(_: boolean): this;
streetViewControl_exists(): boolean;
overviewMapControl(): boolean;
overviewMapControl(_: boolean): this;
overviewMapControl_exists(): boolean;
streetView(): boolean;
streetView(_: boolean): this;
streetView_exists(): boolean;
drawingTools(): boolean;
drawingTools(_: boolean): this;
drawingTools_exists(): boolean;
drawingState(): string;
drawingState(_: string): this;
drawingState_exists(): boolean;
googleMapStyles(): object;
googleMapStyles(_: object): this;
googleMapStyles_exists(): boolean;
}
GMap.prototype.publish("type", "road", "set", "Map Type", ["terrain", "road", "satellite", "hybrid"], { tags: ["Basic"] });
GMap.prototype.publish("centerLat", 42.877742, "number", "Center Latitude", null, { tags: ["Basic"] });
GMap.prototype.publish("centerLong", -97.380979, "number", "Center Longtitude", null, { tags: ["Basic"] });
GMap.prototype.publish("centerAddress", null, "string", "Address to center map on", null, { tags: ["Basic"], optional: true });
GMap.prototype.publish("zoom", 4, "number", "Zoom Level", null, { tags: ["Basic"] });
GMap.prototype.publish("singleZoomToMaxZoom", 14, "number", "Max zoomTo level with single item");
GMap.prototype.publish("panControl", true, "boolean", "Pan Controls", null, { tags: ["Basic"] });
GMap.prototype.publish("zoomControl", true, "boolean", "Zoom Controls", null, { tags: ["Basic"] });
GMap.prototype.publish("scaleControl", true, "boolean", "Scale Controls", null, { tags: ["Basic"] });
GMap.prototype.publish("mapTypeControl", false, "boolean", "Map Type Controls", null, { tags: ["Basic"] });
GMap.prototype.publish("fullscreenControl", false, "boolean", "Fullscreen Controls", null, { tags: ["Basic"] });
GMap.prototype.publish("streetViewControl", false, "boolean", "StreetView Controls", null, { tags: ["Basic"] });
GMap.prototype.publish("overviewMapControl", false, "boolean", "OverviewMap Controls", null, { tags: ["Basic"] });
GMap.prototype.publish("streetView", false, "boolean", "Streetview", null, { tags: ["Basic"] });
GMap.prototype.publish("drawingTools", false, "boolean", "Drawing Tools", null, { tags: ["Basic"] });
GMap.prototype.publish("drawingState", "", "string", "Map Drawings", null, { disable: w => w.drawingTools() === false });
GMap.prototype.publish("googleMapStyles", {}, "object", "Styling for map colors etc", null, { tags: ["Basic"] });