Added TreeHug

This commit is contained in:
vanten-s 2023-11-27 08:40:29 +01:00
parent 50a9332cb8
commit 2ffe913059
Signed by: vanten-s
GPG key ID: DE3060396884D3F2
76 changed files with 24846 additions and 0 deletions

136
src/treehug/frmWrk/.gitignore vendored Normal file
View file

@ -0,0 +1,136 @@
# Byte-compiled / optimized / DLL files
clearLogs.py
__pycache__/
*.py[cod]
*$py.class
test.py
test/
# website/
.vscode/
log.txt
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/

View file

@ -0,0 +1,62 @@
[Installation](#installing)
[Quickstart](#usage)
[Documentation](#documentation)
# Installing
Open terminal in project folder.
Run:
```bash
git clone https://github.com/vanten-s/frmWrk frmWrk
```
Done. Look at usage for quickstart.
# Usage
With a index.html in the working directory this will make host a website at 127.0.0.1
```python
# Example for website
import frmWrk.website as frw
# Start the server on 127.0.0.1:80 with parent folder as root directory
website = frw.WebServer("127.0.0.1", 80, "./")
website.start()
# Close the server when we get input
input()
website.close()
```
# Documentation
```python
# Import everything
import frmWrk
import time
# Create the webserver. Directory is where the server should look for files
website = frmWrk.website.WebServer(ip, port, directory)
# Run the server
website.start()
# Wait 10 seconds and close the server
time.sleep(10)
website.close()
```
frmWrk will replace substrings in the format of {ip:port:prompt} by connecting to (ip, port) and sending "{promt} {path}" where path is the URL that the user is on. Then they replace the whole substring with the response (max 1024 bytes).
frmWrk.databases should not be used.
It Can be used but please dont use it.

View file

@ -0,0 +1,2 @@
import frmWrk.website
import frmWrk.decorators

View file

@ -0,0 +1,192 @@
import socket
import threading
import datetime
enable_logging = True
log_file = "log.txt"
def log(func):
def wrapper(*args, **kwargs):
if not enable_logging: return func(*args, **kwargs)
returnVal = func(*args, **kwargs)
with open(log_file, "a") as f:
try:
if len(returnVal) < 100:
f.write(f"{func.__name__} was called at {datetime.datetime.now().strftime('%m/%d/%Y, %H:%M:%S')} and returned {returnVal}\n")
else:
f.write(f"{func.__name__} was called at {datetime.datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}\n")
except TypeError as e:
f.write(f"{func.__name__} was called at {datetime.datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}\n")
return returnVal
return wrapper
def log_string(string):
if not enable_logging: return string
with open(log_file, "a") as f:
f.write(f"{string}\n")
return string
databases = {}
__using_remote_access = False
"""
Usage:
CREATE <name> # Creates a new database
ADDATTRIBUTE <name> <attribute> # Adds an attribute to a database
ADD <name> <database> # Adds an entry to a database
LIST # Lists all databases
GET <entry> <database> <attribute> # Gets an entry from a database
SET <entry> <database> <attribute> <value> # Sets an attribute of an entry in a database
"""
def help():
return "Usage: CREATE <name> # Creates a new database\nADDATTRIBUTE <name> <attribute> # Adds an attribute to a database\nADD <name> <database> # Adds an entry to a database\nLIST # Lists all databases\nGET <entry> <database> # Gets an entry from a database\nSET <entry> <database> <attribute> <value> # Sets an attribute of an entry in a database"
class Database:
def __init__(self, name):
self.name = name
self.attributes = []
self.entrys = {}
databases[name] = self
def __hash__(self):
return self.name
def __eq__(self, other):
return self.name == other.name
def __str__(self):
return self.name
def __repr__(self):
return self.name
def addAttribute(self, attribute):
self.attributes.append(attribute)
for entry in self.entrys:
entry.__setAttribute(attribute)
def getAttribute(self, attribute):
return self.attributes[attribute]
class Entry:
def __init__(self, name, parent):
self.name = name
self.parent = parent
self.attributes = {attr:None for attr in parent.attributes}
self.parent.entrys[name] = self
def __hash__(self):
return self.name
def __eq__(self, other):
return self.name == other.name
def __str__(self):
return self.attributes.__str__()
def __repr__(self):
return self.name + " with " + self.attributes.__str__()
def __setAttribute(self, attribute):
self.attributes[attribute] = ""
def setAttribute(self, attribute, value):
if attribute in self.attributes.keys():
self.attributes[attribute] = value
else:
raise Exception("Attribute not found")
def getAttribute(self, attribute):
if attribute in self.attributes.keys():
return self.attributes[attribute]
else:
raise Exception("Attribute not found")
def executeInstruction(instruction):
tokens = instruction.split(" ")
log_string(f"Executing instruction: {instruction}")
if tokens[0] == "CREATE":
database = Database(tokens[1])
return database
elif tokens[0] == "ADDATTRIBUTE":
databases[tokens[1]].addAttribute(tokens[2])
elif tokens[0] == "ADD":
Entry(tokens[1], databases[tokens[2]])
elif tokens[0] == "LIST":
if len(databases) == 0:
return "No databases"
if len(tokens) == 2:
return databases[tokens[1]].entrys
if len(tokens) == 3:
return databases[tokens[1]].entrys[tokens[2]]
return databases
elif tokens[0] == "GET":
return databases[tokens[1]].entrys[tokens[2]].getAttribute(tokens[3]).__str__()
elif tokens[0] == "SET":
database = databases[tokens[1]].entrys[tokens[2]].setAttribute(tokens[3], tokens[4])
else:
print(instruction)
return "Invalid instruction"
return "Success"
def __enable_remote_access(ip, port):
HOST = ip # The server's hostname or IP address
PORT = port # The port used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
while True and __using_remote_access:
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024).decode()
firstLine = data.split("\n")[0]
instruction = " ".join(firstLine.split(" ")[1:-1])
conn.send(executeInstruction(instruction).encode())
if not data: break
print('Connection closed')
@log
def enable_remote_access(ip, port):
global __using_remote_access
__using_remote_access = True
t = threading.Thread(target=__enable_remote_access, args=(ip, port))
t.start()
return "Enabled remote access"
def disable_remote_access():
global __using_remote_access
__using_remote_access = False
return "Disabled remote access"

View file

@ -0,0 +1,32 @@
import datetime
enable_logging = True
log_file = "log.txt"
def log(func):
def wrapper(*args, **kwargs):
if not enable_logging: return func(*args, **kwargs)
returnVal = func(*args, **kwargs)
with open(log_file, "a") as f:
try:
if len(returnVal) < 100:
f.write(f"{func.__name__} was called at {datetime.datetime.now().strftime('%m/%d/%Y, %H:%M:%S')} and returned {returnVal}\n")
else:
f.write(f"{func.__name__} was called at {datetime.datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}\n")
except TypeError as e:
f.write(f"{func.__name__} was called at {datetime.datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}\n")
return returnVal
return wrapper
def log_string(string):
if not enable_logging: return string
with open(log_file, "a") as f:
f.write(f"{string}\n")
return string

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,300 @@
import socket
import threading
import datetime
import os
def boron (code: str, path: str) -> str:
total = []
none = []
while "{" in code and "}" in code:
startIndex = code.index("{")
endIndex = code.index("}")
try:
string = code[startIndex+1:endIndex]
ip = string.split(":")[0]
port = int(string.split(":")[1])
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
addr = (ip, port)
print(addr)
s.connect(addr)
s.send((string.split(":")[2] + " " + path).encode('utf-8'))
response = s.recv(1024).decode()
s.close()
code = code.replace("{" + string + "}", response)
print("{" + string + "}")
print(code)
except Exception as e:
print(e)
code = code.replace("{" + string + "}", string)
none.append(string)
print("{" + string + "}")
print(code)
for string in none:
code = code.replace(string, "{" + string + "}")
return code
enable_logging = True
log_file = "log.txt"
def log(func):
def wrapper(*args, **kwargs):
if not enable_logging: return func(*args, **kwargs)
returnVal = func(*args, **kwargs)
try:
if len(returnVal) < 10:
log_string(f"[{datetime.datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}] {func.__name__} was called and returned {returnVal}\n")
else:
log_string(f"[{datetime.datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}] {func.__name__} was called\n")
except TypeError as e:
log_string(f"[{datetime.datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}] {func.__name__} was called\n")
return returnVal
return wrapper
def log_string(string):
global log_file, enable_logging
if not enable_logging: return
with open(log_file, "a") as f:
f.write(f"[{datetime.datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}] {string}\n")
return string
AASCI_404_NOT_FOUND = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
</head>
<body>
<h1>404 Not Found</h1>
<pre style="font-size: xx-large;">
_ _ ___ _ _ _ __ _
| || | / _ \| || | | | / _| | |
| || |_| | | | || |_ _ __ ___ | |_ | |_ ___ _ _ _ __ __| |
|__ _| | | |__ _| | '_ \ / _ \| __| | _/ _ \| | | | '_ \ / _` |
| | | |_| | | | | | | | (_) | |_ | || (_) | |_| | | | | (_| |
|_| \___/ |_| |_| |_|\___/ \__| |_| \___/ \__,_|_| |_|\__,_|</pre>
</body>
"""
content_type = {
'html': 'text/html; charset=\'utf-8\'',
'css': 'text/css; charset=\'utf-8\'',
'js': 'application/javascript; charset=\'utf-8\'',
'xml': 'application/xml; charset=\'utf-8\'',
'png': 'image/png',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'gif': 'image/gif',
'ico': 'image/x-icon',
'svg': 'image/svg+xml',
'json': 'application/json; charset=\'utf-8\'',
'txt': 'text/plain; charset=\'utf-8\'',
'pdf': 'application/pdf',
'zip': 'application/zip',
'rar': 'application/x-rar-compressed',
'7z': 'application/x-7z-compressed',
'doc': 'application/msword',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xls': 'application/vnd.ms-excel',
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'ppt': 'application/vnd.ms-powerpoint',
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'mp3': 'audio/mpeg',
'wav': 'audio/x-wav',
'mp4': 'video/mp4',
'm4v': 'video/x-m4v',
'mov': 'video/quicktime',
'wmv': 'video/x-ms-wmv',
'flv': 'video/x-flv',
'avi': 'video/x-msvideo',
'mkv': 'video/x-matroska',
'm3u': 'application/x-mpegURL',
'm3u8': 'application/vnd.apple.mpegurl',
'ts': 'video/MP2T',
'3gp': 'video/3gpp',
'3g2': 'video/3gpp2',
'mpd': 'video/vnd.mpeg.dash.mpd',
'mp4': 'video/mp4',
'webm': 'video/webm',
'ogv': 'video/ogg',
'ogm': 'video/ogg',
'ogg': 'video/ogg',
'ogx': 'application/ogg',
'oga': 'audio/ogg',
'spx': 'audio/ogg',
'opus': 'audio/ogg',
'flac': 'audio/flac'
}
class WebServer:
def __init__(self, ip, port, directory, site404="/404.html", event_handler=None, overwrites={}, custom_router=None):
self.directory = directory
self.ip = ip
self.port = port
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.s.bind((ip,port))
self.threads = []
self.running = True
self.site404 = directory + "/404.html"
self.event_handler = event_handler
self.overwrites = overwrites
self.custom_router = custom_router
def __getContentType(self, path):
path = path.split(".")[-1]
if not path in content_type.keys():
return 'application/octet-stream'
return content_type[path]
def __getResponse(self, code, content_type, payload, custom_headers={}):
response = b'HTTP/1.1 ' + code.encode("utf-8") + b'\n'
response += b'Content-Type: ' + content_type.encode("utf-8") + b'\n'
response += b'Content-Length: ' + str(len(payload)).encode("utf-8") + b'\n'
response += b'server: frmWrk\n'
for header_key in custom_headers.keys():
response += header_key.encode("utf-8") + custom_headers[header_key].encode("utf-8") + b'\n'
response += b'\n'
response += payload + b'\n\n'
return response
def __get(self, path):
# Remove data after the ?
original = path
if ".." in path:
return self.__getResponse("404 Not Found", self.__getContentType("/index.html"), b'')
if "?" in path:
path = path[:path.index("?")]
if path == "/":
path = "/index.html"
if os.path.isdir(self.directory + path):
if path.endswith("/"):
path = path + "/index.html"
else:
return self.__getResponse("301 Moved Permanently", self.__getContentType("/index.html"), b'', custom_headers={"location: ": path + "/"})
if path in self.overwrites.keys():
return self.__getResponse("200 OK", self.__getContentType(path), self.overwrites[path](original).encode("utf-8"))
path = self.directory + path
try:
with open(path, "rb") as f:
content = f.read()
content_type = self.__getContentType(path)
# if content_type.startswith("text/html"):
# content = boron(content.decode('utf-8'), path).encode('utf-8')
return self.__getResponse("200 OK", content_type, content)
except FileNotFoundError:
if "favicon.ico" in path:
with open(os.path.dirname(os.path.abspath(__file__)) + "/favicon.ico", "rb") as f:
content = f.read()
return self.__getResponse("200 OK", "image/x-icon", content)
try:
print(log_string("404 Not Found: " + path))
with open(self.site404, "rb") as f:
content = f.read()
content_type = self.__getContentType(self.site404)
return self.__getResponse("404 Not Found", content_type, content)
except FileNotFoundError:
return self.__getResponse("404 Not Found", "text/html; charset=\"utf-8\"", AASCI_404_NOT_FOUND.encode("utf-8"))
def __handleRequest(self, request):
tokens = request.split(" ")
method = tokens[0]
path = tokens[1]
version = tokens[2]
if self.event_handler:
self.event_handler(method, (path))
if self.custom_router:
return self.custom_router(method, path, {})
if method == "GET":
return self.__get(path)
return "Only GET Requests Supported Yet Sorry."
def __handleClient(self, c: socket.socket, addr):
global running
while True and self.running:
data = c.recv(1024)
if not data:
break
request = data.decode("utf-8")
response = self.__handleRequest(request)
log_string(f"{addr} asked for {request.split(' ')[1]}")
c.send(response)
c.close()
def __startServer(self):
global running, s
log_string("Server started on port " + str(self.port))
while self.running:
try:
c, addr = self.s.accept()
p = threading.Thread(target=self.__handleClient, args=(c, addr))
p.start()
self.threads.append(p)
except Exception as e:
log_string(e)
def start(self):
print("Starting server...")
self.s.listen(1)
t = threading.Thread(target=self.__startServer)
t.start()
self.main_process = t
def close(self):
self.running = False
print("Closing server...")
self.s.close()
for thread in self.threads:
thread.join()
self.main_process.join()
print("Server closed")

18
src/treehug/index.html Normal file
View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>TreeHug</title>
<script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
<script src="node_modules/@googlemaps/markerclusterer/dist/index.min.js"></script>
<link rel="stylesheet" type="text/css" href="./style.css" />
</head>
<body>
<div id="map"></div>
<!-- prettier-ignore -->
<script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
({key: "AIzaSyCneCqjzobnZ-ksYh85eosk1whovucTjQE", v: "weekly"});</script>
<script type="module" src="./index.js"></script>
</body>
</html>

164
src/treehug/index.js Normal file
View file

@ -0,0 +1,164 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var RADIUS = 2000.0;
var MAX_RESULTS = 20;
var INCLUDED_TYPES = ["park"];
var map;
var pos = { lat: 30, lng: -110 };
function initMap() {
return __awaiter(this, void 0, void 0, function () {
var Map;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, google.maps.importLibrary("maps")];
case 1:
Map = (_a.sent()).Map;
map = new Map(document.getElementById("map"), {
center: pos,
zoom: 8,
mapId: "DEMO_MAP",
});
return [2 /*return*/];
}
});
});
}
function find_places(position) {
return __awaiter(this, void 0, void 0, function () {
var _a, AdvancedMarkerElement, PinElement, InfoWindow, response, locations, infoWindow, labels, markers;
return __generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, google.maps.importLibrary("marker")];
case 1:
_a = _b.sent(), AdvancedMarkerElement = _a.AdvancedMarkerElement, PinElement = _a.PinElement;
return [4 /*yield*/, google.maps.importLibrary("maps")];
case 2:
InfoWindow = (_b.sent()).InfoWindow;
return [4 /*yield*/, fetch("https://places.googleapis.com/v1/places:searchNearby", {
method: "POST",
headers: new Headers({
"Content-Type": "application/json",
"X-Goog-Api-Key": "AIzaSyCneCqjzobnZ-ksYh85eosk1whovucTjQE",
"X-Goog-FieldMask": "places.location,places.displayName",
}),
body: JSON.stringify({
includedTypes: INCLUDED_TYPES,
maxResultCount: MAX_RESULTS.toString(),
locationRestriction: {
circle: {
center: {
latitude: position.lat,
longitude: position.lng,
},
radius: RADIUS.toString(),
},
},
})
})];
case 3:
response = _b.sent();
return [4 /*yield*/, response.json()];
case 4:
locations = _b.sent();
infoWindow = new google.maps.InfoWindow({
content: "",
disableAutoPan: true,
});
console.log(locations);
labels = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
markers = locations["places"].map(function (element, i) {
var place = element["location"];
var latitude = place["latitude"];
var longitude = place["longitude"];
var label = labels[i % labels.length];
var pinGlyph = new google.maps.marker.PinElement({
glyph: label,
glyphColor: "white"
});
var marker = new google.maps.marker.AdvancedMarkerElement({
position: {
lat: latitude,
lng: longitude,
},
content: pinGlyph.element,
});
marker.addListener("click", function () {
infoWindow.setContent(position.lat + ", " + position.lng);
infoWindow.open(map, marker);
});
return marker;
});
// @ts-ignore
new markerClusterer.MarkerClusterer({ markers: markers, map: map });
return [2 /*return*/];
}
});
});
}
function handle_geolocation() {
return __awaiter(this, void 0, void 0, function () {
var AdvancedMarkerElement;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, google.maps.importLibrary("marker")];
case 1:
AdvancedMarkerElement = (_a.sent()).AdvancedMarkerElement;
navigator.geolocation.getCurrentPosition(function (position) {
pos = {
lat: position.coords.latitude,
lng: position.coords.longitude,
};
map.setCenter(pos);
map.setZoom(15);
find_places(pos);
new AdvancedMarkerElement({
map: map,
position: pos,
title: "You",
});
});
return [2 /*return*/];
}
});
});
}
initMap().then(function () {
if (navigator.geolocation) {
handle_geolocation();
}
});

109
src/treehug/index.ts Normal file
View file

@ -0,0 +1,109 @@
const RADIUS = 2000.0;
const MAX_RESULTS = 20;
const INCLUDED_TYPES = ["park"];
let map: google.maps.Map;
let pos: google.maps.LatLngLiteral = {lat: 30, lng: -110};
async function initMap() {
const { Map } = await google.maps.importLibrary("maps") as google.maps.MapsLibrary;
map = new Map(document.getElementById("map") as HTMLElement, {
center: pos,
zoom: 8,
mapId: "DEMO_MAP",
});
}
async function find_places(position: google.maps.LatLngLiteral) {
// @ts-ignore
const { AdvancedMarkerElement, PinElement } = await google.maps.importLibrary("marker") as google.maps.MarkerLibrary;
const { InfoWindow } = await google.maps.importLibrary("maps") as google.maps.MapsLibrary;
let response = await fetch("https://places.googleapis.com/v1/places:searchNearby", {
method: "POST",
headers: new Headers({
"Content-Type": "application/json",
"X-Goog-Api-Key": "AIzaSyCneCqjzobnZ-ksYh85eosk1whovucTjQE",
"X-Goog-FieldMask": "places.location,places.displayName",
}),
body: JSON.stringify({
includedTypes: INCLUDED_TYPES,
maxResultCount: MAX_RESULTS.toString(),
locationRestriction: {
circle: {
center: {
latitude: position.lat,
longitude: position.lng,
},
radius: RADIUS.toString(),
},
},
})
});
let locations: {places: [{location: {latitude: number, longitude: number}, displayName: {text: string}}]} = await response.json();
const infoWindow = new google.maps.InfoWindow({
content: "",
disableAutoPan: true,
});
console.log(locations);
const labels = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let markers = locations["places"].map((element, i) => {
let place = element["location"];
let latitude = place["latitude"];
let longitude = place["longitude"];
let label = labels[i % labels.length];
const pinGlyph = new google.maps.marker.PinElement({
glyph: label,
glyphColor: "white"
});
const marker = new google.maps.marker.AdvancedMarkerElement({
position: {
lat: latitude,
lng: longitude,
},
content: pinGlyph.element,
});
marker.addListener("click", () => {
infoWindow.setContent(position.lat + ", " + position.lng);
infoWindow.open(map, marker);
});
return marker;
});
// @ts-ignore
new markerClusterer.MarkerClusterer({ markers, map });
}
async function handle_geolocation() {
const { AdvancedMarkerElement } = await google.maps.importLibrary("marker") as google.maps.MarkerLibrary;
navigator.geolocation.getCurrentPosition(
(position: GeolocationPosition) => {
pos = {
lat: position.coords.latitude,
lng: position.coords.longitude,
};
map.setCenter(pos)
map.setZoom(15);
find_places(pos);
new AdvancedMarkerElement({
map: map,
position: pos,
title: "You",
})
}
);
}
initMap().then(() => {
if (navigator.geolocation) {
handle_geolocation();
}
});

4
src/treehug/main.py Normal file
View file

@ -0,0 +1,4 @@
import frmWrk.website
webserver = frmWrk.website.WebServer("127.0.0.1", 4000, "./")
webserver.start()

40
src/treehug/node_modules/.package-lock.json generated vendored Normal file
View file

@ -0,0 +1,40 @@
{
"name": "TreeHug",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/@googlemaps/markerclusterer": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.5.0.tgz",
"integrity": "sha512-WpHLCZxP7QmB4Hc5kyODGdTfJPsZiOIbcvbYhcS/VeiRNDVjf6CRQ8ViQjwrG5OySC66rtOdj4RVhUXsd1tNTQ==",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"supercluster": "^8.0.1"
}
},
"node_modules/@types/google.maps": {
"version": "3.54.6",
"resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.54.6.tgz",
"integrity": "sha512-cTGbsddDgEwZ/6xPywI7HKbeYJkfXFg92HdYnlE0HO3gT/030CVdUHLO2uQOkEo0YMp6TRPqTlAnRGNbQJu8vw==",
"dev": true
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/kdbush": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
"integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="
},
"node_modules/supercluster": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
"integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
"dependencies": {
"kdbush": "^4.0.2"
}
}
}
}

View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,105 @@
# Google Maps JavaScript MarkerClusterer
[![npm](https://img.shields.io/npm/v/@googlemaps/markerclusterer)](https://www.npmjs.com/package/@googlemaps/markerclusterer)
![Build](https://github.com/googlemaps/js-markerclusterer/workflows/Test/badge.svg)
![Release](https://github.com/googlemaps/js-markerclusterer/workflows/Release/badge.svg)
[![codecov](https://codecov.io/gh/googlemaps/js-markerclusterer/branch/main/graph/badge.svg)](https://codecov.io/gh/googlemaps/js-markerclusterer)
![GitHub contributors](https://img.shields.io/github/contributors/googlemaps/js-markerclusterer?color=green)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![](https://github.com/jpoehnelt/in-solidarity-bot/raw/main/static//badge-flat.png)](https://github.com/apps/in-solidarity)
## Description
The library creates and manages per-zoom-level clusters for large amounts of markers.
[**Try the demo**](https://googlemaps.github.io/js-markerclusterer/public/defaults/)
![screenshot](https://user-images.githubusercontent.com/3392975/135143029-20abd824-0f3e-4e28-bad3-327acf7aec04.png)
See the [history section](#history) and [migration section](#migration) for how this library relates to [@google/markerclusterer][@google/markerclusterer] and [@googlemaps/markerclustererplus][@googlemaps/markerclustererplus].
## Install
Available via npm as the package [@googlemaps/markerclusterer](https://www.npmjs.com/package/@googlemaps/markerclusterer).
```
npm i @googlemaps/markerclusterer
```
Alternativly you may add the umd package directly to the html document using the unpkg link.
```html
<script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"></script>
```
When adding via unpkg, the `MarkerClusterer` can be accessed at `markerClusterer.MarkerClusterer`.
#### TypeScript
This library uses the official TypeScript typings for Google Maps Platform, [@types/google.maps](https://www.npmjs.com/package/@types/google.maps).
```sh
npm i -D @types/google.maps
```
## Documentation
The [reference documentation](https://googlemaps.github.io/js-markerclusterer/) is generated from the TypeScript definitions.
## Examples
```js
import { MarkerClusterer } from "@googlemaps/markerclusterer";
// use default algorithm and renderer
const markerCluster = new MarkerClusterer({ map, markers });
```
View the package in action:
- [Algorithm Comparisons](https://googlemaps.github.io/js-markerclusterer/public/algorithms) - This example demonstrates the different algorithms. Please note that spacing and many other options can be changed for each algorithm.
- [Renderer Usage](https://googlemaps.github.io/js-markerclusterer/public/renderers) - This example demonstrates different renderers similar to the image below.
![Screen Shot 2021-09-28 at 1 41 06 PM](https://user-images.githubusercontent.com/3392975/135154898-a5abb5a4-3022-44e0-92d2-5dcefa247e87.png)
## History
This library has a heritage in [@google/markerclusterer][@google/markerclusterer] and [@googlemaps/markerclustererplus][@googlemaps/markerclustererplus], originally made available on [code.google.com](https://code.google.com/archive/) and then transferred to GitHub at https://github.com/googlemaps/v3-utility-library. The following is an approximate timeline.
- 201X - [@google/markerclusterer][@google/markerclusterer] was created.
- 201X - [@googlemaps/markerclustererplus][@googlemaps/markerclustererplus] was created.
- 2019 - Libraries were published to NPM.
- 2019 - [@google/markerclusterer][@google/markerclusterer] was deprecated for [@googlemaps/markerclustererplus][@googlemaps/markerclustererplus].
- 2020 - [@googlemaps/markerclustererplus][@googlemaps/markerclustererplus] was refactored to TypeScript.
- 2020 - [@googlemaps/markerclustererplus][@googlemaps/markerclustererplus] was moved to a separate repository.
- 2021 - [@googlemaps/markerclustererplus][@googlemaps/markerclustererplus] was rewritten as [@googlemaps/markerclusterer (**new**)][@googlemaps/markerclusterer].
- TBD - [@googlemaps/markerclustererplus][@googlemaps/markerclustererplus] is deprecated for [@googlemaps/markerclusterer (**new**)][@googlemaps/markerclusterer].
## Migration
The API of [@googlemaps/markerclusterer][@googlemaps/markerclusterer] has changed in a number of ways from [@googlemaps/markerclustererplus][@googlemaps/markerclustererplus].
- The `MarkerClusterer` class now accepts an `algorithm` and `renderer` parameter to allow for more flexibility. The interface looks like the following:
```js
{
algorithm?: Algorithm;
map?: google.maps.Map;
markers?: google.maps.Marker[];
renderer?: Renderer;
onClusterClick?: onClusterClickHandler;
}
```
- The `MarkerClusterer` accepts a single options argument instead of positional parameters.
- The traditional `GridAlgorithm` is still supported, **but is not the default**. The default is [supercluster](https://www.npmjs.com/package/supercluster) which uses [k-d trees](https://en.wikipedia.org/wiki/K-d_tree) for improved performance.
- Styling of clusters has been simplifed and moved to the renderer interface.
- The `MarkerClusterer` class is still an instance of `google.maps.OverlayView`, but uses `google.maps.Marker`s instead of `google.maps.Overlay` to render the clusters. This solves issues related to the usage of map panes and click handlers.
- @googlemaps/markerclusterer supports Marker and Map [a11y improvements](https://cloud.google.com/blog/products/maps-platform/improved-accessibility-maps-javascript-api).
[@googlemaps/markerclustererplus]: https://www.npmjs.com/package/@googlemaps/markerclustererplus
[@google/markerclusterer]: https://www.npmjs.com/package/@google/markerclusterer
[@googlemaps/markerclusterer]: https://www.npmjs.com/package/@googlemaps/markerclusterer

View file

@ -0,0 +1,113 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types="google.maps" />
import { Cluster } from "../cluster";
import { Marker } from "../marker-utils";
export interface AlgorithmInput {
/**
* The map containing the markers and clusters.
*/
map: google.maps.Map;
/**
* An array of markers to be clustered.
*
* There are some specific edge cases to be aware of including the following:
* * Markers that are not visible.
*/
markers: Marker[];
/**
* The `mapCanvasProjection` enables easy conversion from lat/lng to pixel.
*
* @see [MapCanvasProjection](https://developers.google.com/maps/documentation/javascript/reference/overlay-view#MapCanvasProjection)
*/
mapCanvasProjection: google.maps.MapCanvasProjection;
}
export interface AlgorithmOutput {
/**
* The clusters returned based upon the {@link AlgorithmInput}.
*/
clusters: Cluster[];
/**
* A boolean flag indicating that the clusters have not changed.
*/
changed?: boolean;
}
export interface Algorithm {
/**
* Calculates an array of {@link Cluster}.
*/
calculate: ({ markers, map }: AlgorithmInput) => AlgorithmOutput;
}
export interface AlgorithmOptions {
maxZoom?: number;
}
/**
* @hidden
*/
export declare abstract class AbstractAlgorithm implements Algorithm {
protected maxZoom: number;
constructor({ maxZoom }: AlgorithmOptions);
/**
* Helper function to bypass clustering based upon some map state such as
* zoom, number of markers, etc.
*
* ```typescript
* cluster({markers, map}: AlgorithmInput): Cluster[] {
* if (shouldBypassClustering(map)) {
* return this.noop({markers})
* }
* }
* ```
*/
protected noop<T extends Pick<AlgorithmInput, "markers">>({ markers, }: T): Cluster[];
/**
* Calculates an array of {@link Cluster}. Calculate is separate from
* {@link cluster} as it does preprocessing on the markers such as filtering
* based upon the viewport as in {@link AbstractViewportAlgorithm}. Caching
* and other optimizations can also be done here.
*/
abstract calculate({ markers, map }: AlgorithmInput): AlgorithmOutput;
/**
* Clusters the markers and called from {@link calculate}.
*/
protected abstract cluster({ markers, map }: AlgorithmInput): Cluster[];
}
/**
* @hidden
*/
export interface ViewportAlgorithmOptions extends AlgorithmOptions {
/**
* The number of pixels to extend beyond the viewport bounds when filtering
* markers prior to clustering.
*/
viewportPadding?: number;
}
/**
* Abstract viewport algorithm proves a class to filter markers by a padded
* viewport. This is a common optimization.
*
* @hidden
*/
export declare abstract class AbstractViewportAlgorithm extends AbstractAlgorithm {
protected viewportPadding: number;
constructor({ viewportPadding, ...options }: ViewportAlgorithmOptions);
calculate({ markers, map, mapCanvasProjection, }: AlgorithmInput): AlgorithmOutput;
protected abstract cluster({ markers, map }: AlgorithmInput): Cluster[];
}
/**
* @hidden
*/
export declare const noop: (markers: Marker[]) => Cluster[];

View file

@ -0,0 +1,16 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};

View file

@ -0,0 +1,46 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types="google.maps" />
import { AbstractViewportAlgorithm, AlgorithmInput, AlgorithmOutput, ViewportAlgorithmOptions } from "./core";
import { Cluster } from "../cluster";
import { Marker } from "../marker-utils";
export interface GridOptions extends ViewportAlgorithmOptions {
gridSize?: number;
/**
* Max distance between cluster center and point in meters.
* @default 10000
*/
maxDistance?: number;
}
/**
* The default Grid algorithm historically used in Google Maps marker
* clustering.
*
* The Grid algorithm does not implement caching and markers may flash as the
* viewport changes. Instead use {@link SuperClusterAlgorithm}.
*/
export declare class GridAlgorithm extends AbstractViewportAlgorithm {
protected gridSize: number;
protected maxDistance: number;
protected clusters: Cluster[];
protected state: {
zoom: number;
};
constructor({ maxDistance, gridSize, ...options }: GridOptions);
calculate({ markers, map, mapCanvasProjection, }: AlgorithmInput): AlgorithmOutput;
protected cluster({ markers, map, mapCanvasProjection, }: AlgorithmInput): Cluster[];
protected addToClosestCluster(marker: Marker, map: google.maps.Map, projection: google.maps.MapCanvasProjection): void;
}

View file

@ -0,0 +1,16 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};

View file

@ -0,0 +1,21 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from "./core";
export * from "./grid";
export * from "./noop";
export * from "./supercluster";
export * from "./superviewport";
export * from "./utils";

View file

@ -0,0 +1,25 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AbstractAlgorithm, AlgorithmInput, AlgorithmOptions, AlgorithmOutput } from "./core";
import { Cluster } from "../cluster";
/**
* Noop algorithm does not generate any clusters or filter markers by the an extended viewport.
*/
export declare class NoopAlgorithm extends AbstractAlgorithm {
constructor({ ...options }: AlgorithmOptions);
calculate({ markers, map, mapCanvasProjection, }: AlgorithmInput): AlgorithmOutput;
protected cluster(input: AlgorithmInput): Cluster[];
}

View file

@ -0,0 +1,43 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AbstractAlgorithm, AlgorithmInput, AlgorithmOutput } from "./core";
import SuperCluster, { ClusterFeature } from "supercluster";
import { Marker } from "../marker-utils";
import { Cluster } from "../cluster";
export type SuperClusterOptions = SuperCluster.Options<{
[name: string]: any;
}, {
[name: string]: any;
}>;
/**
* A very fast JavaScript algorithm for geospatial point clustering using KD trees.
*
* @see https://www.npmjs.com/package/supercluster for more information on options.
*/
export declare class SuperClusterAlgorithm extends AbstractAlgorithm {
protected superCluster: SuperCluster;
protected markers: Marker[];
protected clusters: Cluster[];
protected state: {
zoom: number;
};
constructor({ maxZoom, radius, ...options }: SuperClusterOptions);
calculate(input: AlgorithmInput): AlgorithmOutput;
cluster({ map }: AlgorithmInput): Cluster[];
protected transformCluster({ geometry: { coordinates: [lng, lat], }, properties, }: ClusterFeature<{
marker: Marker;
}>): Cluster;
}

View file

@ -0,0 +1,16 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};

View file

@ -0,0 +1,43 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AbstractViewportAlgorithm, AlgorithmInput, AlgorithmOutput, ViewportAlgorithmOptions } from "./core";
import { SuperClusterOptions } from "./supercluster";
import SuperCluster, { ClusterFeature } from "supercluster";
import { Marker } from "../marker-utils";
import { Cluster } from "../cluster";
export interface SuperClusterViewportOptions extends SuperClusterOptions, ViewportAlgorithmOptions {
}
export interface SuperClusterViewportState {
zoom: number;
view: [number, number, number, number];
}
/**
* A very fast JavaScript algorithm for geospatial point clustering using KD trees.
*
* @see https://www.npmjs.com/package/supercluster for more information on options.
*/
export declare class SuperClusterViewportAlgorithm extends AbstractViewportAlgorithm {
protected superCluster: SuperCluster;
protected markers: Marker[];
protected clusters: Cluster[];
protected state: SuperClusterViewportState;
constructor({ maxZoom, radius, viewportPadding, ...options }: SuperClusterViewportOptions);
calculate(input: AlgorithmInput): AlgorithmOutput;
cluster({ map, mapCanvasProjection }: AlgorithmInput): Cluster[];
protected transformCluster({ geometry: { coordinates: [lng, lat], }, properties, }: ClusterFeature<{
marker: Marker;
}>): Cluster;
}

View file

@ -0,0 +1,16 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};

View file

@ -0,0 +1,56 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types="google.maps" />
import { Marker } from "../marker-utils";
/**
* Returns the markers visible in a padded map viewport
*
* @param map
* @param mapCanvasProjection
* @param markers The list of marker to filter
* @param viewportPaddingPixels The padding in pixel
* @returns The list of markers in the padded viewport
*/
export declare const filterMarkersToPaddedViewport: (map: google.maps.Map, mapCanvasProjection: google.maps.MapCanvasProjection, markers: Marker[], viewportPaddingPixels: number) => Marker[];
/**
* Extends a bounds by a number of pixels in each direction
*/
export declare const extendBoundsToPaddedViewport: (bounds: google.maps.LatLngBounds, projection: google.maps.MapCanvasProjection, numPixels: number) => google.maps.LatLngBounds;
/**
* Gets the extended bounds as a bbox [westLng, southLat, eastLng, northLat]
*/
export declare const getPaddedViewport: (bounds: google.maps.LatLngBounds, projection: google.maps.MapCanvasProjection, pixels: number) => [number, number, number, number];
/**
* Returns the distance between 2 positions.
*
* @hidden
*/
export declare const distanceBetweenPoints: (p1: google.maps.LatLngLiteral, p2: google.maps.LatLngLiteral) => number;
type PixelBounds = {
northEast: google.maps.Point;
southWest: google.maps.Point;
};
/**
* Extends a pixel bounds by numPixels in all directions.
*
* @hidden
*/
export declare const extendPixelBounds: ({ northEast, southWest }: PixelBounds, numPixels: number) => PixelBounds;
/**
* @hidden
*/
export declare const pixelBoundsToLatLngBounds: ({ northEast, southWest }: PixelBounds, projection: google.maps.MapCanvasProjection) => google.maps.LatLngBounds;
export {};

View file

@ -0,0 +1,16 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};

View file

@ -0,0 +1,41 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types="google.maps" />
import { Marker } from "./marker-utils";
export interface ClusterOptions {
position?: google.maps.LatLng | google.maps.LatLngLiteral;
markers?: Marker[];
}
export declare class Cluster {
marker?: Marker;
readonly markers?: Marker[];
protected _position: google.maps.LatLng;
constructor({ markers, position }: ClusterOptions);
get bounds(): google.maps.LatLngBounds | undefined;
get position(): google.maps.LatLng;
/**
* Get the count of **visible** markers.
*/
get count(): number;
/**
* Add a marker to the cluster.
*/
push(marker: Marker): void;
/**
* Cleanup references and remove marker from map.
*/
delete(): void;
}

View file

@ -0,0 +1,16 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};

View file

@ -0,0 +1,20 @@
/**
* Copyright 2019 Google LLC. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from "./algorithms";
export * from "./cluster";
export * from "./markerclusterer";
export * from "./renderer";
export * from "./marker-utils";

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,956 @@
import equal from 'fast-deep-equal';
import SuperCluster from 'supercluster';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* util class that creates a common set of convenience functions to wrap
* shared behavior of Advanced Markers and Markers.
*/
class MarkerUtils {
static isAdvancedMarkerAvailable(map) {
return (google.maps.marker &&
map.getMapCapabilities().isAdvancedMarkersAvailable === true);
}
static isAdvancedMarker(marker) {
return (google.maps.marker &&
marker instanceof google.maps.marker.AdvancedMarkerElement);
}
static setMap(marker, map) {
if (this.isAdvancedMarker(marker)) {
marker.map = map;
}
else {
marker.setMap(map);
}
}
static getPosition(marker) {
// SuperClusterAlgorithm.calculate expects a LatLng instance so we fake it for Adv Markers
if (this.isAdvancedMarker(marker)) {
if (marker.position) {
if (marker.position instanceof google.maps.LatLng) {
return marker.position;
}
// since we can't cast to LatLngLiteral for reasons =(
if (marker.position.lat && marker.position.lng) {
return new google.maps.LatLng(marker.position.lat, marker.position.lng);
}
}
return new google.maps.LatLng(null);
}
return marker.getPosition();
}
static getVisible(marker) {
if (this.isAdvancedMarker(marker)) {
/**
* Always return true for Advanced Markers because the clusterer
* uses getVisible as a way to count legacy markers not as an actual
* indicator of visibility for some reason. Even when markers are hidden
* Marker.getVisible returns `true` and this is used to set the marker count
* on the cluster. See the behavior of Cluster.count
*/
return true;
}
return marker.getVisible();
}
}
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class Cluster {
constructor({ markers, position }) {
this.markers = markers;
if (position) {
if (position instanceof google.maps.LatLng) {
this._position = position;
}
else {
this._position = new google.maps.LatLng(position);
}
}
}
get bounds() {
if (this.markers.length === 0 && !this._position) {
return;
}
const bounds = new google.maps.LatLngBounds(this._position, this._position);
for (const marker of this.markers) {
bounds.extend(MarkerUtils.getPosition(marker));
}
return bounds;
}
get position() {
return this._position || this.bounds.getCenter();
}
/**
* Get the count of **visible** markers.
*/
get count() {
return this.markers.filter((m) => MarkerUtils.getVisible(m)).length;
}
/**
* Add a marker to the cluster.
*/
push(marker) {
this.markers.push(marker);
}
/**
* Cleanup references and remove marker from map.
*/
delete() {
if (this.marker) {
MarkerUtils.setMap(this.marker, null);
this.marker = undefined;
}
this.markers.length = 0;
}
}
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Returns the markers visible in a padded map viewport
*
* @param map
* @param mapCanvasProjection
* @param markers The list of marker to filter
* @param viewportPaddingPixels The padding in pixel
* @returns The list of markers in the padded viewport
*/
const filterMarkersToPaddedViewport = (map, mapCanvasProjection, markers, viewportPaddingPixels) => {
const extendedMapBounds = extendBoundsToPaddedViewport(map.getBounds(), mapCanvasProjection, viewportPaddingPixels);
return markers.filter((marker) => extendedMapBounds.contains(MarkerUtils.getPosition(marker)));
};
/**
* Extends a bounds by a number of pixels in each direction
*/
const extendBoundsToPaddedViewport = (bounds, projection, numPixels) => {
const { northEast, southWest } = latLngBoundsToPixelBounds(bounds, projection);
const extendedPixelBounds = extendPixelBounds({ northEast, southWest }, numPixels);
return pixelBoundsToLatLngBounds(extendedPixelBounds, projection);
};
/**
* Gets the extended bounds as a bbox [westLng, southLat, eastLng, northLat]
*/
const getPaddedViewport = (bounds, projection, pixels) => {
const extended = extendBoundsToPaddedViewport(bounds, projection, pixels);
const ne = extended.getNorthEast();
const sw = extended.getSouthWest();
return [sw.lng(), sw.lat(), ne.lng(), ne.lat()];
};
/**
* Returns the distance between 2 positions.
*
* @hidden
*/
const distanceBetweenPoints = (p1, p2) => {
const R = 6371; // Radius of the Earth in km
const dLat = ((p2.lat - p1.lat) * Math.PI) / 180;
const dLon = ((p2.lng - p1.lng) * Math.PI) / 180;
const sinDLat = Math.sin(dLat / 2);
const sinDLon = Math.sin(dLon / 2);
const a = sinDLat * sinDLat +
Math.cos((p1.lat * Math.PI) / 180) *
Math.cos((p2.lat * Math.PI) / 180) *
sinDLon *
sinDLon;
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
};
/**
* Converts a LatLng bound to pixels.
*
* @hidden
*/
const latLngBoundsToPixelBounds = (bounds, projection) => {
return {
northEast: projection.fromLatLngToDivPixel(bounds.getNorthEast()),
southWest: projection.fromLatLngToDivPixel(bounds.getSouthWest()),
};
};
/**
* Extends a pixel bounds by numPixels in all directions.
*
* @hidden
*/
const extendPixelBounds = ({ northEast, southWest }, numPixels) => {
northEast.x += numPixels;
northEast.y -= numPixels;
southWest.x -= numPixels;
southWest.y += numPixels;
return { northEast, southWest };
};
/**
* @hidden
*/
const pixelBoundsToLatLngBounds = ({ northEast, southWest }, projection) => {
const sw = projection.fromDivPixelToLatLng(southWest);
const ne = projection.fromDivPixelToLatLng(northEast);
return new google.maps.LatLngBounds(sw, ne);
};
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @hidden
*/
class AbstractAlgorithm {
constructor({ maxZoom = 16 }) {
this.maxZoom = maxZoom;
}
/**
* Helper function to bypass clustering based upon some map state such as
* zoom, number of markers, etc.
*
* ```typescript
* cluster({markers, map}: AlgorithmInput): Cluster[] {
* if (shouldBypassClustering(map)) {
* return this.noop({markers})
* }
* }
* ```
*/
noop({ markers, }) {
return noop(markers);
}
}
/**
* Abstract viewport algorithm proves a class to filter markers by a padded
* viewport. This is a common optimization.
*
* @hidden
*/
class AbstractViewportAlgorithm extends AbstractAlgorithm {
constructor(_a) {
var { viewportPadding = 60 } = _a, options = __rest(_a, ["viewportPadding"]);
super(options);
this.viewportPadding = 60;
this.viewportPadding = viewportPadding;
}
calculate({ markers, map, mapCanvasProjection, }) {
if (map.getZoom() >= this.maxZoom) {
return {
clusters: this.noop({
markers,
}),
changed: false,
};
}
return {
clusters: this.cluster({
markers: filterMarkersToPaddedViewport(map, mapCanvasProjection, markers, this.viewportPadding),
map,
mapCanvasProjection,
}),
};
}
}
/**
* @hidden
*/
const noop = (markers) => {
const clusters = markers.map((marker) => new Cluster({
position: MarkerUtils.getPosition(marker),
markers: [marker],
}));
return clusters;
};
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* The default Grid algorithm historically used in Google Maps marker
* clustering.
*
* The Grid algorithm does not implement caching and markers may flash as the
* viewport changes. Instead use {@link SuperClusterAlgorithm}.
*/
class GridAlgorithm extends AbstractViewportAlgorithm {
constructor(_a) {
var { maxDistance = 40000, gridSize = 40 } = _a, options = __rest(_a, ["maxDistance", "gridSize"]);
super(options);
this.clusters = [];
this.state = { zoom: -1 };
this.maxDistance = maxDistance;
this.gridSize = gridSize;
}
calculate({ markers, map, mapCanvasProjection, }) {
const state = { zoom: map.getZoom() };
let changed = false;
if (this.state.zoom >= this.maxZoom && state.zoom >= this.maxZoom) ;
else {
changed = !equal(this.state, state);
}
this.state = state;
if (map.getZoom() >= this.maxZoom) {
return {
clusters: this.noop({
markers,
}),
changed,
};
}
return {
clusters: this.cluster({
markers: filterMarkersToPaddedViewport(map, mapCanvasProjection, markers, this.viewportPadding),
map,
mapCanvasProjection,
}),
};
}
cluster({ markers, map, mapCanvasProjection, }) {
this.clusters = [];
markers.forEach((marker) => {
this.addToClosestCluster(marker, map, mapCanvasProjection);
});
return this.clusters;
}
addToClosestCluster(marker, map, projection) {
let maxDistance = this.maxDistance; // Some large number
let cluster = null;
for (let i = 0; i < this.clusters.length; i++) {
const candidate = this.clusters[i];
const distance = distanceBetweenPoints(candidate.bounds.getCenter().toJSON(), MarkerUtils.getPosition(marker).toJSON());
if (distance < maxDistance) {
maxDistance = distance;
cluster = candidate;
}
}
if (cluster &&
extendBoundsToPaddedViewport(cluster.bounds, projection, this.gridSize).contains(MarkerUtils.getPosition(marker))) {
cluster.push(marker);
}
else {
const cluster = new Cluster({ markers: [marker] });
this.clusters.push(cluster);
}
}
}
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Noop algorithm does not generate any clusters or filter markers by the an extended viewport.
*/
class NoopAlgorithm extends AbstractAlgorithm {
constructor(_a) {
var options = __rest(_a, []);
super(options);
}
calculate({ markers, map, mapCanvasProjection, }) {
return {
clusters: this.cluster({ markers, map, mapCanvasProjection }),
changed: false,
};
}
cluster(input) {
return this.noop(input);
}
}
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A very fast JavaScript algorithm for geospatial point clustering using KD trees.
*
* @see https://www.npmjs.com/package/supercluster for more information on options.
*/
class SuperClusterAlgorithm extends AbstractAlgorithm {
constructor(_a) {
var { maxZoom, radius = 60 } = _a, options = __rest(_a, ["maxZoom", "radius"]);
super({ maxZoom });
this.state = { zoom: -1 };
this.superCluster = new SuperCluster(Object.assign({ maxZoom: this.maxZoom, radius }, options));
}
calculate(input) {
let changed = false;
const state = { zoom: input.map.getZoom() };
if (!equal(input.markers, this.markers)) {
changed = true;
// TODO use proxy to avoid copy?
this.markers = [...input.markers];
const points = this.markers.map((marker) => {
const position = MarkerUtils.getPosition(marker);
const coordinates = [position.lng(), position.lat()];
return {
type: "Feature",
geometry: {
type: "Point",
coordinates,
},
properties: { marker },
};
});
this.superCluster.load(points);
}
if (!changed) {
if (this.state.zoom <= this.maxZoom || state.zoom <= this.maxZoom) {
changed = !equal(this.state, state);
}
}
this.state = state;
if (changed) {
this.clusters = this.cluster(input);
}
return { clusters: this.clusters, changed };
}
cluster({ map }) {
return this.superCluster
.getClusters([-180, -90, 180, 90], Math.round(map.getZoom()))
.map((feature) => this.transformCluster(feature));
}
transformCluster({ geometry: { coordinates: [lng, lat], }, properties, }) {
if (properties.cluster) {
return new Cluster({
markers: this.superCluster
.getLeaves(properties.cluster_id, Infinity)
.map((leaf) => leaf.properties.marker),
position: { lat, lng },
});
}
const marker = properties.marker;
return new Cluster({
markers: [marker],
position: MarkerUtils.getPosition(marker),
});
}
}
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A very fast JavaScript algorithm for geospatial point clustering using KD trees.
*
* @see https://www.npmjs.com/package/supercluster for more information on options.
*/
class SuperClusterViewportAlgorithm extends AbstractViewportAlgorithm {
constructor(_a) {
var { maxZoom, radius = 60, viewportPadding = 60 } = _a, options = __rest(_a, ["maxZoom", "radius", "viewportPadding"]);
super({ maxZoom, viewportPadding });
this.superCluster = new SuperCluster(Object.assign({ maxZoom: this.maxZoom, radius }, options));
this.state = { zoom: -1, view: [0, 0, 0, 0] };
}
calculate(input) {
const state = {
zoom: Math.round(input.map.getZoom()),
view: getPaddedViewport(input.map.getBounds(), input.mapCanvasProjection, this.viewportPadding),
};
let changed = !equal(this.state, state);
if (!equal(input.markers, this.markers)) {
changed = true;
// TODO use proxy to avoid copy?
this.markers = [...input.markers];
const points = this.markers.map((marker) => {
const position = MarkerUtils.getPosition(marker);
const coordinates = [position.lng(), position.lat()];
return {
type: "Feature",
geometry: {
type: "Point",
coordinates,
},
properties: { marker },
};
});
this.superCluster.load(points);
}
if (changed) {
this.clusters = this.cluster(input);
this.state = state;
}
return { clusters: this.clusters, changed };
}
cluster({ map, mapCanvasProjection }) {
/* recalculate new state because we can't use the cached version. */
const state = {
zoom: Math.round(map.getZoom()),
view: getPaddedViewport(map.getBounds(), mapCanvasProjection, this.viewportPadding),
};
return this.superCluster
.getClusters(state.view, state.zoom)
.map((feature) => this.transformCluster(feature));
}
transformCluster({ geometry: { coordinates: [lng, lat], }, properties, }) {
if (properties.cluster) {
return new Cluster({
markers: this.superCluster
.getLeaves(properties.cluster_id, Infinity)
.map((leaf) => leaf.properties.marker),
position: { lat, lng },
});
}
const marker = properties.marker;
return new Cluster({
markers: [marker],
position: MarkerUtils.getPosition(marker),
});
}
}
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Provides statistics on all clusters in the current render cycle for use in {@link Renderer.render}.
*/
class ClusterStats {
constructor(markers, clusters) {
this.markers = { sum: markers.length };
const clusterMarkerCounts = clusters.map((a) => a.count);
const clusterMarkerSum = clusterMarkerCounts.reduce((a, b) => a + b, 0);
this.clusters = {
count: clusters.length,
markers: {
mean: clusterMarkerSum / clusters.length,
sum: clusterMarkerSum,
min: Math.min(...clusterMarkerCounts),
max: Math.max(...clusterMarkerCounts),
},
};
}
}
class DefaultRenderer {
/**
* The default render function for the library used by {@link MarkerClusterer}.
*
* Currently set to use the following:
*
* ```typescript
* // change color if this cluster has more markers than the mean cluster
* const color =
* count > Math.max(10, stats.clusters.markers.mean)
* ? "#ff0000"
* : "#0000ff";
*
* // create svg url with fill color
* const svg = window.btoa(`
* <svg fill="${color}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
* <circle cx="120" cy="120" opacity=".6" r="70" />
* <circle cx="120" cy="120" opacity=".3" r="90" />
* <circle cx="120" cy="120" opacity=".2" r="110" />
* <circle cx="120" cy="120" opacity=".1" r="130" />
* </svg>`);
*
* // create marker using svg icon
* return new google.maps.Marker({
* position,
* icon: {
* url: `data:image/svg+xml;base64,${svg}`,
* scaledSize: new google.maps.Size(45, 45),
* },
* label: {
* text: String(count),
* color: "rgba(255,255,255,0.9)",
* fontSize: "12px",
* },
* // adjust zIndex to be above other markers
* zIndex: 1000 + count,
* });
* ```
*/
render({ count, position }, stats, map) {
// change color if this cluster has more markers than the mean cluster
const color = count > Math.max(10, stats.clusters.markers.mean) ? "#ff0000" : "#0000ff";
// create svg literal with fill color
const svg = `<svg fill="${color}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" width="50" height="50">
<circle cx="120" cy="120" opacity=".6" r="70" />
<circle cx="120" cy="120" opacity=".3" r="90" />
<circle cx="120" cy="120" opacity=".2" r="110" />
<text x="50%" y="50%" style="fill:#fff" text-anchor="middle" font-size="50" dominant-baseline="middle" font-family="roboto,arial,sans-serif">${count}</text>
</svg>`;
const title = `Cluster of ${count} markers`,
// adjust zIndex to be above other markers
zIndex = Number(google.maps.Marker.MAX_ZINDEX) + count;
if (MarkerUtils.isAdvancedMarkerAvailable(map)) {
// create cluster SVG element
const parser = new DOMParser();
const svgEl = parser.parseFromString(svg, "image/svg+xml").documentElement;
svgEl.setAttribute("transform", "translate(0 25)");
const clusterOptions = {
map,
position,
zIndex,
title,
content: svgEl,
};
return new google.maps.marker.AdvancedMarkerElement(clusterOptions);
}
const clusterOptions = {
position,
zIndex,
title,
icon: {
url: `data:image/svg+xml;base64,${btoa(svg)}`,
anchor: new google.maps.Point(25, 25),
},
};
return new google.maps.Marker(clusterOptions);
}
}
/**
* Copyright 2019 Google LLC. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Extends an object's prototype by another's.
*
* @param type1 The Type to be extended.
* @param type2 The Type to extend with.
* @ignore
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function extend(type1, type2) {
/* istanbul ignore next */
// eslint-disable-next-line prefer-const
for (let property in type2.prototype) {
type1.prototype[property] = type2.prototype[property];
}
}
/**
* @ignore
*/
class OverlayViewSafe {
constructor() {
// MarkerClusterer implements google.maps.OverlayView interface. We use the
// extend function to extend MarkerClusterer with google.maps.OverlayView
// because it might not always be available when the code is defined so we
// look for it at the last possible moment. If it doesn't exist now then
// there is no point going ahead :)
extend(OverlayViewSafe, google.maps.OverlayView);
}
}
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var MarkerClustererEvents;
(function (MarkerClustererEvents) {
MarkerClustererEvents["CLUSTERING_BEGIN"] = "clusteringbegin";
MarkerClustererEvents["CLUSTERING_END"] = "clusteringend";
MarkerClustererEvents["CLUSTER_CLICK"] = "click";
})(MarkerClustererEvents || (MarkerClustererEvents = {}));
const defaultOnClusterClickHandler = (_, cluster, map) => {
map.fitBounds(cluster.bounds);
};
/**
* MarkerClusterer creates and manages per-zoom-level clusters for large amounts
* of markers. See {@link MarkerClustererOptions} for more details.
*
*/
class MarkerClusterer extends OverlayViewSafe {
constructor({ map, markers = [], algorithmOptions = {}, algorithm = new SuperClusterAlgorithm(algorithmOptions), renderer = new DefaultRenderer(), onClusterClick = defaultOnClusterClickHandler, }) {
super();
this.markers = [...markers];
this.clusters = [];
this.algorithm = algorithm;
this.renderer = renderer;
this.onClusterClick = onClusterClick;
if (map) {
this.setMap(map);
}
}
addMarker(marker, noDraw) {
if (this.markers.includes(marker)) {
return;
}
this.markers.push(marker);
if (!noDraw) {
this.render();
}
}
addMarkers(markers, noDraw) {
markers.forEach((marker) => {
this.addMarker(marker, true);
});
if (!noDraw) {
this.render();
}
}
removeMarker(marker, noDraw) {
const index = this.markers.indexOf(marker);
if (index === -1) {
// Marker is not in our list of markers, so do nothing:
return false;
}
MarkerUtils.setMap(marker, null);
this.markers.splice(index, 1); // Remove the marker from the list of managed markers
if (!noDraw) {
this.render();
}
return true;
}
removeMarkers(markers, noDraw) {
let removed = false;
markers.forEach((marker) => {
removed = this.removeMarker(marker, true) || removed;
});
if (removed && !noDraw) {
this.render();
}
return removed;
}
clearMarkers(noDraw) {
this.markers.length = 0;
if (!noDraw) {
this.render();
}
}
/**
* Recalculates and draws all the marker clusters.
*/
render() {
const map = this.getMap();
if (map instanceof google.maps.Map && map.getProjection()) {
google.maps.event.trigger(this, MarkerClustererEvents.CLUSTERING_BEGIN, this);
const { clusters, changed } = this.algorithm.calculate({
markers: this.markers,
map,
mapCanvasProjection: this.getProjection(),
});
// Allow algorithms to return flag on whether the clusters/markers have changed.
if (changed || changed == undefined) {
// Accumulate the markers of the clusters composed of a single marker.
// Those clusters directly use the marker.
// Clusters with more than one markers use a group marker generated by a renderer.
const singleMarker = new Set();
for (const cluster of clusters) {
if (cluster.markers.length == 1) {
singleMarker.add(cluster.markers[0]);
}
}
const groupMarkers = [];
// Iterate the clusters that are currently rendered.
for (const cluster of this.clusters) {
if (cluster.marker == null) {
continue;
}
if (cluster.markers.length == 1) {
if (!singleMarker.has(cluster.marker)) {
// The marker:
// - was previously rendered because it is from a cluster with 1 marker,
// - should no more be rendered as it is not in singleMarker.
MarkerUtils.setMap(cluster.marker, null);
}
}
else {
// Delay the removal of old group markers to avoid flickering.
groupMarkers.push(cluster.marker);
}
}
this.clusters = clusters;
this.renderClusters();
// Delayed removal of the markers of the former groups.
requestAnimationFrame(() => groupMarkers.forEach((marker) => MarkerUtils.setMap(marker, null)));
}
google.maps.event.trigger(this, MarkerClustererEvents.CLUSTERING_END, this);
}
}
onAdd() {
this.idleListener = this.getMap().addListener("idle", this.render.bind(this));
this.render();
}
onRemove() {
google.maps.event.removeListener(this.idleListener);
this.reset();
}
reset() {
this.markers.forEach((marker) => MarkerUtils.setMap(marker, null));
this.clusters.forEach((cluster) => cluster.delete());
this.clusters = [];
}
renderClusters() {
// Generate stats to pass to renderers.
const stats = new ClusterStats(this.markers, this.clusters);
const map = this.getMap();
this.clusters.forEach((cluster) => {
if (cluster.markers.length === 1) {
cluster.marker = cluster.markers[0];
}
else {
// Generate the marker to represent the group.
cluster.marker = this.renderer.render(cluster, stats, map);
// Make sure all individual markers are removed from the map.
cluster.markers.forEach((marker) => MarkerUtils.setMap(marker, null));
if (this.onClusterClick) {
cluster.marker.addListener("click",
/* istanbul ignore next */
(event) => {
google.maps.event.trigger(this, MarkerClustererEvents.CLUSTER_CLICK, cluster);
this.onClusterClick(event, cluster, map);
});
}
}
MarkerUtils.setMap(cluster.marker, map);
});
}
}
export { AbstractAlgorithm, AbstractViewportAlgorithm, Cluster, ClusterStats, DefaultRenderer, GridAlgorithm, MarkerClusterer, MarkerClustererEvents, MarkerUtils, NoopAlgorithm, SuperClusterAlgorithm, SuperClusterViewportAlgorithm, defaultOnClusterClickHandler, distanceBetweenPoints, extendBoundsToPaddedViewport, extendPixelBounds, filterMarkersToPaddedViewport, getPaddedViewport, noop, pixelBoundsToLatLngBounds };
//# sourceMappingURL=index.esm.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,31 @@
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types="google.maps" />
/**
* Supports markers of either either "legacy" or "advanced" types.
*/
export type Marker = google.maps.Marker | google.maps.marker.AdvancedMarkerElement;
/**
* util class that creates a common set of convenience functions to wrap
* shared behavior of Advanced Markers and Markers.
*/
export declare class MarkerUtils {
static isAdvancedMarkerAvailable(map: google.maps.Map): boolean;
static isAdvancedMarker(marker: Marker): marker is google.maps.marker.AdvancedMarkerElement;
static setMap(marker: Marker, map: google.maps.Map | null): void;
static getPosition(marker: Marker): google.maps.LatLng;
static getVisible(marker: Marker): boolean;
}

View file

@ -0,0 +1,16 @@
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};

View file

@ -0,0 +1,77 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types="google.maps" />
import { Algorithm, AlgorithmOptions } from "./algorithms";
import { Renderer } from "./renderer";
import { Cluster } from "./cluster";
import { OverlayViewSafe } from "./overlay-view-safe";
import { Marker } from "./marker-utils";
export type onClusterClickHandler = (event: google.maps.MapMouseEvent, cluster: Cluster, map: google.maps.Map) => void;
export interface MarkerClustererOptions {
markers?: Marker[];
/**
* An algorithm to cluster markers. Default is {@link SuperClusterAlgorithm}. Must
* provide a `calculate` method accepting {@link AlgorithmInput} and returning
* an array of {@link Cluster}.
*/
algorithm?: Algorithm;
algorithmOptions?: AlgorithmOptions;
map?: google.maps.Map | null;
/**
* An object that converts a {@link Cluster} into a `google.maps.Marker`.
* Default is {@link DefaultRenderer}.
*/
renderer?: Renderer;
onClusterClick?: onClusterClickHandler;
}
export declare enum MarkerClustererEvents {
CLUSTERING_BEGIN = "clusteringbegin",
CLUSTERING_END = "clusteringend",
CLUSTER_CLICK = "click"
}
export declare const defaultOnClusterClickHandler: onClusterClickHandler;
/**
* MarkerClusterer creates and manages per-zoom-level clusters for large amounts
* of markers. See {@link MarkerClustererOptions} for more details.
*
*/
export declare class MarkerClusterer extends OverlayViewSafe {
/** @see {@link MarkerClustererOptions.onClusterClick} */
onClusterClick: onClusterClickHandler;
/** @see {@link MarkerClustererOptions.algorithm} */
protected algorithm: Algorithm;
protected clusters: Cluster[];
protected markers: Marker[];
/** @see {@link MarkerClustererOptions.renderer} */
protected renderer: Renderer;
/** @see {@link MarkerClustererOptions.map} */
protected map: google.maps.Map | null;
protected idleListener: google.maps.MapsEventListener;
constructor({ map, markers, algorithmOptions, algorithm, renderer, onClusterClick, }: MarkerClustererOptions);
addMarker(marker: Marker, noDraw?: boolean): void;
addMarkers(markers: Marker[], noDraw?: boolean): void;
removeMarker(marker: Marker, noDraw?: boolean): boolean;
removeMarkers(markers: Marker[], noDraw?: boolean): boolean;
clearMarkers(noDraw?: boolean): void;
/**
* Recalculates and draws all the marker clusters.
*/
render(): void;
onAdd(): void;
onRemove(): void;
protected reset(): void;
protected renderClusters(): void;
}

View file

@ -0,0 +1,16 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};

View file

@ -0,0 +1,24 @@
/**
* Copyright 2019 Google LLC. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types="google.maps" />
export interface OverlayViewSafe extends google.maps.OverlayView {
}
/**
* @ignore
*/
export declare class OverlayViewSafe {
constructor();
}

View file

@ -0,0 +1,92 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types="google.maps" />
import { Cluster } from "./cluster";
import { Marker } from "./marker-utils";
/**
* Provides statistics on all clusters in the current render cycle for use in {@link Renderer.render}.
*/
export declare class ClusterStats {
readonly markers: {
sum: number;
};
readonly clusters: {
count: number;
markers: {
mean: number;
sum: number;
min: number;
max: number;
};
};
constructor(markers: Marker[], clusters: Cluster[]);
}
export interface Renderer {
/**
* Turn a {@link Cluster} into a `Marker`.
*
* Below is a simple example to create a marker with the number of markers in the cluster as a label.
*
* ```typescript
* return new google.maps.Marker({
* position,
* label: String(markers.length),
* });
* ```
*/
render(cluster: Cluster, stats: ClusterStats, map: google.maps.Map): Marker;
}
export declare class DefaultRenderer implements Renderer {
/**
* The default render function for the library used by {@link MarkerClusterer}.
*
* Currently set to use the following:
*
* ```typescript
* // change color if this cluster has more markers than the mean cluster
* const color =
* count > Math.max(10, stats.clusters.markers.mean)
* ? "#ff0000"
* : "#0000ff";
*
* // create svg url with fill color
* const svg = window.btoa(`
* <svg fill="${color}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
* <circle cx="120" cy="120" opacity=".6" r="70" />
* <circle cx="120" cy="120" opacity=".3" r="90" />
* <circle cx="120" cy="120" opacity=".2" r="110" />
* <circle cx="120" cy="120" opacity=".1" r="130" />
* </svg>`);
*
* // create marker using svg icon
* return new google.maps.Marker({
* position,
* icon: {
* url: `data:image/svg+xml;base64,${svg}`,
* scaledSize: new google.maps.Size(45, 45),
* },
* label: {
* text: String(count),
* color: "rgba(255,255,255,0.9)",
* fontSize: "12px",
* },
* // adjust zIndex to be above other markers
* zIndex: 1000 + count,
* });
* ```
*/
render({ count, position }: Cluster, stats: ClusterStats, map: google.maps.Map): Marker;
}

View file

@ -0,0 +1,80 @@
{
"name": "@googlemaps/markerclusterer",
"version": "2.5.0",
"description": "Creates and manages per-zoom-level clusters for large amounts of markers.",
"keywords": [
"cluster",
"google",
"maps",
"marker"
],
"homepage": "https://github.com/googlemaps/js-markerclusterer",
"bugs": {
"url": "https://github.com/googlemaps/js-markerclusterer/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/googlemaps/js-markerclusterer.git"
},
"license": "Apache-2.0",
"author": "Justin Poehnelt",
"main": "dist/index.umd.js",
"unpkg": "dist/index.min.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"files": [
"dist/*"
],
"scripts": {
"docs": "typedoc src/index.ts && cp -r dist docs/dist && npm run examples && cp -r public docs/public",
"examples": "rollup -c rollup.config.examples.js",
"dev": "rollup -c rollup.config.examples.js --watch",
"format": "eslint . --fix",
"lint": "eslint .",
"prepare": "rm -rf dist && rollup -c",
"test": "jest --passWithNoTests src/*",
"test:all": "jest"
},
"dependencies": {
"fast-deep-equal": "^3.1.3",
"supercluster": "^8.0.1"
},
"devDependencies": {
"@babel/preset-env": "^7.11.5",
"@babel/runtime-corejs3": "^7.11.2",
"@googlemaps/jest-mocks": "^2.19.1",
"@googlemaps/js-api-loader": "^1.12.3",
"@rollup/plugin-babel": "^6.0.0",
"@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-html": "^1.0.0",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.0",
"@rollup/plugin-typescript": "^11.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/google.maps": "^3.53.1",
"@types/jest": "^27.0.1",
"@types/supercluster": "^7.1.0",
"@typescript-eslint/eslint-plugin": ">=4.1.0",
"@typescript-eslint/parser": ">=4.1.0",
"core-js": "^3.6.5",
"d3-interpolate": "^3.0.1",
"eslint": "^8.41.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jest": "^27.0.1",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^26.4.2",
"prettier": "^2.1.1",
"rollup": "^2.26.11",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-serve": "^2.0.2",
"rollup-plugin-terser": "^7.0.2",
"selenium-webdriver": "^4.0.0-alpha.7",
"ts-jest": "^26.3.0",
"typedoc": "^0.25.0",
"typescript": "^4.0.2"
},
"publishConfig": {
"access": "public",
"registry": "https://wombat-dressing-room.appspot.com"
}
}

21
src/treehug/node_modules/@types/google.maps/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

15
src/treehug/node_modules/@types/google.maps/README.md generated vendored Normal file
View file

@ -0,0 +1,15 @@
# Installation
> `npm install --save @types/google.maps`
# Summary
This package contains type definitions for google.maps (https://developers.google.com/maps/).
# Details
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/google.maps.
### Additional Details
* Last updated: Mon, 30 Oct 2023 23:45:38 GMT
* Dependencies: none
# Credits
These definitions were written by [Alex Muramoto](https://github.com/amuramoto), and [Angela Yu](https://github.com/wangela).

13401
src/treehug/node_modules/@types/google.maps/index.d.ts generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,31 @@
{
"name": "@types/google.maps",
"version": "3.54.6",
"description": "TypeScript definitions for google.maps",
"homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/google.maps",
"license": "MIT",
"contributors": [
{
"name": "Alex Muramoto",
"githubUsername": "amuramoto",
"url": "https://github.com/amuramoto"
},
{
"name": "Angela Yu",
"githubUsername": "wangela",
"url": "https://github.com/wangela"
}
],
"main": "",
"types": "index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git",
"directory": "types/google.maps"
},
"scripts": {},
"dependencies": {},
"typesPublisherContentHash": "6e988627cc4ed80cdd9a3876fea3f6eeb7c90d42440b0c053cb9ed920406ffbc",
"typeScriptVersion": "4.5",
"nonNpm": true
}

21
src/treehug/node_modules/fast-deep-equal/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Evgeny Poberezkin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

96
src/treehug/node_modules/fast-deep-equal/README.md generated vendored Normal file
View file

@ -0,0 +1,96 @@
# fast-deep-equal
The fastest deep equal with ES6 Map, Set and Typed arrays support.
[![Build Status](https://travis-ci.org/epoberezkin/fast-deep-equal.svg?branch=master)](https://travis-ci.org/epoberezkin/fast-deep-equal)
[![npm](https://img.shields.io/npm/v/fast-deep-equal.svg)](https://www.npmjs.com/package/fast-deep-equal)
[![Coverage Status](https://coveralls.io/repos/github/epoberezkin/fast-deep-equal/badge.svg?branch=master)](https://coveralls.io/github/epoberezkin/fast-deep-equal?branch=master)
## Install
```bash
npm install fast-deep-equal
```
## Features
- ES5 compatible
- works in node.js (8+) and browsers (IE9+)
- checks equality of Date and RegExp objects by value.
ES6 equal (`require('fast-deep-equal/es6')`) also supports:
- Maps
- Sets
- Typed arrays
## Usage
```javascript
var equal = require('fast-deep-equal');
console.log(equal({foo: 'bar'}, {foo: 'bar'})); // true
```
To support ES6 Maps, Sets and Typed arrays equality use:
```javascript
var equal = require('fast-deep-equal/es6');
console.log(equal(Int16Array([1, 2]), Int16Array([1, 2]))); // true
```
To use with React (avoiding the traversal of React elements' _owner
property that contains circular references and is not needed when
comparing the elements - borrowed from [react-fast-compare](https://github.com/FormidableLabs/react-fast-compare)):
```javascript
var equal = require('fast-deep-equal/react');
var equal = require('fast-deep-equal/es6/react');
```
## Performance benchmark
Node.js v12.6.0:
```
fast-deep-equal x 261,950 ops/sec ±0.52% (89 runs sampled)
fast-deep-equal/es6 x 212,991 ops/sec ±0.34% (92 runs sampled)
fast-equals x 230,957 ops/sec ±0.83% (85 runs sampled)
nano-equal x 187,995 ops/sec ±0.53% (88 runs sampled)
shallow-equal-fuzzy x 138,302 ops/sec ±0.49% (90 runs sampled)
underscore.isEqual x 74,423 ops/sec ±0.38% (89 runs sampled)
lodash.isEqual x 36,637 ops/sec ±0.72% (90 runs sampled)
deep-equal x 2,310 ops/sec ±0.37% (90 runs sampled)
deep-eql x 35,312 ops/sec ±0.67% (91 runs sampled)
ramda.equals x 12,054 ops/sec ±0.40% (91 runs sampled)
util.isDeepStrictEqual x 46,440 ops/sec ±0.43% (90 runs sampled)
assert.deepStrictEqual x 456 ops/sec ±0.71% (88 runs sampled)
The fastest is fast-deep-equal
```
To run benchmark (requires node.js 6+):
```bash
npm run benchmark
```
__Please note__: this benchmark runs against the available test cases. To choose the most performant library for your application, it is recommended to benchmark against your data and to NOT expect this benchmark to reflect the performance difference in your application.
## Enterprise support
fast-deep-equal package is a part of [Tidelift enterprise subscription](https://tidelift.com/subscription/pkg/npm-fast-deep-equal?utm_source=npm-fast-deep-equal&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) - it provides a centralised commercial support to open-source software users, in addition to the support provided by software maintainers.
## Security contact
To report a security vulnerability, please use the
[Tidelift security contact](https://tidelift.com/security).
Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerability via GitHub issues.
## License
[MIT](https://github.com/epoberezkin/fast-deep-equal/blob/master/LICENSE)

View file

@ -0,0 +1,2 @@
declare const equal: (a: any, b: any) => boolean;
export = equal;

72
src/treehug/node_modules/fast-deep-equal/es6/index.js generated vendored Normal file
View file

@ -0,0 +1,72 @@
'use strict';
// do not edit .js files directly - edit src/index.jst
var envHasBigInt64Array = typeof BigInt64Array !== 'undefined';
module.exports = function equal(a, b) {
if (a === b) return true;
if (a && b && typeof a == 'object' && typeof b == 'object') {
if (a.constructor !== b.constructor) return false;
var length, i, keys;
if (Array.isArray(a)) {
length = a.length;
if (length != b.length) return false;
for (i = length; i-- !== 0;)
if (!equal(a[i], b[i])) return false;
return true;
}
if ((a instanceof Map) && (b instanceof Map)) {
if (a.size !== b.size) return false;
for (i of a.entries())
if (!b.has(i[0])) return false;
for (i of a.entries())
if (!equal(i[1], b.get(i[0]))) return false;
return true;
}
if ((a instanceof Set) && (b instanceof Set)) {
if (a.size !== b.size) return false;
for (i of a.entries())
if (!b.has(i[0])) return false;
return true;
}
if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
length = a.length;
if (length != b.length) return false;
for (i = length; i-- !== 0;)
if (a[i] !== b[i]) return false;
return true;
}
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
keys = Object.keys(a);
length = keys.length;
if (length !== Object.keys(b).length) return false;
for (i = length; i-- !== 0;)
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
for (i = length; i-- !== 0;) {
var key = keys[i];
if (!equal(a[key], b[key])) return false;
}
return true;
}
// true if both NaN, false otherwise
return a!==a && b!==b;
};

View file

@ -0,0 +1,2 @@
declare const equal: (a: any, b: any) => boolean;
export = equal;

79
src/treehug/node_modules/fast-deep-equal/es6/react.js generated vendored Normal file
View file

@ -0,0 +1,79 @@
'use strict';
// do not edit .js files directly - edit src/index.jst
var envHasBigInt64Array = typeof BigInt64Array !== 'undefined';
module.exports = function equal(a, b) {
if (a === b) return true;
if (a && b && typeof a == 'object' && typeof b == 'object') {
if (a.constructor !== b.constructor) return false;
var length, i, keys;
if (Array.isArray(a)) {
length = a.length;
if (length != b.length) return false;
for (i = length; i-- !== 0;)
if (!equal(a[i], b[i])) return false;
return true;
}
if ((a instanceof Map) && (b instanceof Map)) {
if (a.size !== b.size) return false;
for (i of a.entries())
if (!b.has(i[0])) return false;
for (i of a.entries())
if (!equal(i[1], b.get(i[0]))) return false;
return true;
}
if ((a instanceof Set) && (b instanceof Set)) {
if (a.size !== b.size) return false;
for (i of a.entries())
if (!b.has(i[0])) return false;
return true;
}
if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
length = a.length;
if (length != b.length) return false;
for (i = length; i-- !== 0;)
if (a[i] !== b[i]) return false;
return true;
}
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
keys = Object.keys(a);
length = keys.length;
if (length !== Object.keys(b).length) return false;
for (i = length; i-- !== 0;)
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
for (i = length; i-- !== 0;) {
var key = keys[i];
if (key === '_owner' && a.$$typeof) {
// React-specific: avoid traversing React elements' _owner.
// _owner contains circular references
// and is not needed when comparing the actual elements (and not their owners)
continue;
}
if (!equal(a[key], b[key])) return false;
}
return true;
}
// true if both NaN, false otherwise
return a!==a && b!==b;
};

4
src/treehug/node_modules/fast-deep-equal/index.d.ts generated vendored Normal file
View file

@ -0,0 +1,4 @@
declare module 'fast-deep-equal' {
const equal: (a: any, b: any) => boolean;
export = equal;
}

46
src/treehug/node_modules/fast-deep-equal/index.js generated vendored Normal file
View file

@ -0,0 +1,46 @@
'use strict';
// do not edit .js files directly - edit src/index.jst
module.exports = function equal(a, b) {
if (a === b) return true;
if (a && b && typeof a == 'object' && typeof b == 'object') {
if (a.constructor !== b.constructor) return false;
var length, i, keys;
if (Array.isArray(a)) {
length = a.length;
if (length != b.length) return false;
for (i = length; i-- !== 0;)
if (!equal(a[i], b[i])) return false;
return true;
}
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
keys = Object.keys(a);
length = keys.length;
if (length !== Object.keys(b).length) return false;
for (i = length; i-- !== 0;)
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
for (i = length; i-- !== 0;) {
var key = keys[i];
if (!equal(a[key], b[key])) return false;
}
return true;
}
// true if both NaN, false otherwise
return a!==a && b!==b;
};

61
src/treehug/node_modules/fast-deep-equal/package.json generated vendored Normal file
View file

@ -0,0 +1,61 @@
{
"name": "fast-deep-equal",
"version": "3.1.3",
"description": "Fast deep equal",
"main": "index.js",
"scripts": {
"eslint": "eslint *.js benchmark/*.js spec/*.js",
"build": "node build",
"benchmark": "npm i && npm run build && cd ./benchmark && npm i && node ./",
"test-spec": "mocha spec/*.spec.js -R spec",
"test-cov": "nyc npm run test-spec",
"test-ts": "tsc --target ES5 --noImplicitAny index.d.ts",
"test": "npm run build && npm run eslint && npm run test-ts && npm run test-cov",
"prepublish": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/epoberezkin/fast-deep-equal.git"
},
"keywords": [
"fast",
"equal",
"deep-equal"
],
"author": "Evgeny Poberezkin",
"license": "MIT",
"bugs": {
"url": "https://github.com/epoberezkin/fast-deep-equal/issues"
},
"homepage": "https://github.com/epoberezkin/fast-deep-equal#readme",
"devDependencies": {
"coveralls": "^3.1.0",
"dot": "^1.1.2",
"eslint": "^7.2.0",
"mocha": "^7.2.0",
"nyc": "^15.1.0",
"pre-commit": "^1.2.2",
"react": "^16.12.0",
"react-test-renderer": "^16.12.0",
"sinon": "^9.0.2",
"typescript": "^3.9.5"
},
"nyc": {
"exclude": [
"**/spec/**",
"node_modules"
],
"reporter": [
"lcov",
"text-summary"
]
},
"files": [
"index.js",
"index.d.ts",
"react.js",
"react.d.ts",
"es6/"
],
"types": "index.d.ts"
}

2
src/treehug/node_modules/fast-deep-equal/react.d.ts generated vendored Normal file
View file

@ -0,0 +1,2 @@
declare const equal: (a: any, b: any) => boolean;
export = equal;

53
src/treehug/node_modules/fast-deep-equal/react.js generated vendored Normal file
View file

@ -0,0 +1,53 @@
'use strict';
// do not edit .js files directly - edit src/index.jst
module.exports = function equal(a, b) {
if (a === b) return true;
if (a && b && typeof a == 'object' && typeof b == 'object') {
if (a.constructor !== b.constructor) return false;
var length, i, keys;
if (Array.isArray(a)) {
length = a.length;
if (length != b.length) return false;
for (i = length; i-- !== 0;)
if (!equal(a[i], b[i])) return false;
return true;
}
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
keys = Object.keys(a);
length = keys.length;
if (length !== Object.keys(b).length) return false;
for (i = length; i-- !== 0;)
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
for (i = length; i-- !== 0;) {
var key = keys[i];
if (key === '_owner' && a.$$typeof) {
// React-specific: avoid traversing React elements' _owner.
// _owner contains circular references
// and is not needed when comparing the actual elements (and not their owners)
continue;
}
if (!equal(a[key], b[key])) return false;
}
return true;
}
// true if both NaN, false otherwise
return a!==a && b!==b;
};

15
src/treehug/node_modules/kdbush/LICENSE generated vendored Normal file
View file

@ -0,0 +1,15 @@
ISC License
Copyright (c) 2018, Vladimir Agafonkin
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

102
src/treehug/node_modules/kdbush/README.md generated vendored Normal file
View file

@ -0,0 +1,102 @@
## KDBush
A very fast static spatial index for 2D points based on a flat KD-tree.
Compared to [RBush](https://github.com/mourner/rbush):
- **Points only** — no rectangles.
- **Static** — you can't add/remove items after initial indexing.
- **Faster** indexing and search, with lower **memory** footprint.
- Index is stored as a single **array buffer** (so you can [transfer](https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects) it between threads or store it as a compact file).
If you need a static index for rectangles, not only points, see [Flatbush](https://github.com/mourner/flatbush). When indexing points, KDBush has the advantage of taking ~2x less memory than Flatbush.
[![Build Status](https://github.com/mourner/kdbush/workflows/Node/badge.svg?branch=master)](https://github.com/mourner/kdbush/actions)
[![Simply Awesome](https://img.shields.io/badge/simply-awesome-brightgreen.svg)](https://github.com/mourner/projects)
## Usage
```js
// initialize KDBush for 1000 items
const index = new KDBush(1000);
// fill it with 1000 points
for (const {x, y} of items) {
index.add(x, y);
}
// perform the indexing
index.finish();
// make a bounding box query
const foundIds = index.range(minX, minY, maxX, maxY);
// map ids to original items
const foundItems = foundIds.map(i => items[i]);
// make a radius query
const neighborIds = index.within(x, y, 5);
// instantly transfer the index from a worker to the main thread
postMessage(index.data, [index.data]);
// reconstruct the index from a raw array buffer
const index = KDBush.from(e.data);
```
## Install
Install with NPM: `npm install kdbush`, then import as a module:
```js
import KDBush from 'kdbush';
```
Or use as a module directly in the browser with [jsDelivr](https://www.jsdelivr.com/esm):
```html
<script type="module">
import KDBush from 'https://cdn.jsdelivr.net/npm/kdbush/+esm';
</script>
```
Alternatively, there's a browser bundle with a `KDBush` global variable:
```html
<script src="https://cdn.jsdelivr.net/npm/kdbush"></script>
```
## API
#### new KDBush(numItems[, nodeSize, ArrayType])
Creates an index that will hold a given number of points (`numItems`). Additionally accepts:
- `nodeSize`: Size of the KD-tree node, `64` by default. Higher means faster indexing but slower search, and vise versa.
- `ArrayType`: Array type to use for storing coordinate values. `Float64Array` by default, but if your coordinates are integer values, `Int32Array` makes the index faster and smaller.
#### index.add(x, y)
Adds a given point to the index. Returns a zero-based, incremental number that represents the newly added point.
#### index.range(minX, minY, maxX, maxY)
Finds all items within the given bounding box and returns an array of indices that refer to the order the items were added (the values returned by `index.add(x, y)`).
#### index.within(x, y, radius)
Finds all items within a given radius from the query point and returns an array of indices.
#### `KDBush.from(data)`
Recreates a KDBush index from raw `ArrayBuffer` data
(that's exposed as `index.data` on a previously indexed KDBush instance).
Very useful for transferring or sharing indices between threads or storing them in a file.
### Properties
- `data`: array buffer that holds the index.
- `numItems`: number of stored items.
- `nodeSize`: number of items in a KD-tree node.
- `ArrayType`: array type used for internal coordinates storage.
- `IndexArrayType`: array type used for internal item indices storage.

53
src/treehug/node_modules/kdbush/index.d.ts generated vendored Normal file
View file

@ -0,0 +1,53 @@
export default class KDBush {
/**
* Creates an index from raw `ArrayBuffer` data.
* @param {ArrayBuffer} data
*/
static from(data: ArrayBuffer): KDBush;
/**
* Creates an index that will hold a given number of items.
* @param {number} numItems
* @param {number} [nodeSize=64] Size of the KD-tree node (64 by default).
* @param {TypedArrayConstructor} [ArrayType=Float64Array] The array type used for coordinates storage (`Float64Array` by default).
* @param {ArrayBuffer} [data] (For internal use only)
*/
constructor(numItems: number, nodeSize?: number | undefined, ArrayType?: TypedArrayConstructor | undefined, data?: ArrayBuffer | undefined);
numItems: number;
nodeSize: number;
ArrayType: TypedArrayConstructor;
IndexArrayType: Uint16ArrayConstructor | Uint32ArrayConstructor;
data: ArrayBuffer;
ids: Uint16Array | Uint32Array;
coords: Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array;
_pos: number;
_finished: boolean;
/**
* Add a point to the index.
* @param {number} x
* @param {number} y
* @returns {number} An incremental index associated with the added item (starting from `0`).
*/
add(x: number, y: number): number;
/**
* Perform indexing of the added points.
*/
finish(): KDBush;
/**
* Search the index for items within a given bounding box.
* @param {number} minX
* @param {number} minY
* @param {number} maxX
* @param {number} maxY
* @returns {number[]} An array of indices correponding to the found items.
*/
range(minX: number, minY: number, maxX: number, maxY: number): number[];
/**
* Search the index for items within a given radius.
* @param {number} qx
* @param {number} qy
* @param {number} r Query radius.
* @returns {number[]} An array of indices correponding to the found items.
*/
within(qx: number, qy: number, r: number): number[];
}
export type TypedArrayConstructor = Int8ArrayConstructor | Uint8ArrayConstructor | Uint8ClampedArrayConstructor | Int16ArrayConstructor | Uint16ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor;

327
src/treehug/node_modules/kdbush/index.js generated vendored Normal file
View file

@ -0,0 +1,327 @@
const ARRAY_TYPES = [
Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array,
Int32Array, Uint32Array, Float32Array, Float64Array
];
/** @typedef {Int8ArrayConstructor | Uint8ArrayConstructor | Uint8ClampedArrayConstructor | Int16ArrayConstructor | Uint16ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor} TypedArrayConstructor */
const VERSION = 1; // serialized format version
const HEADER_SIZE = 8;
export default class KDBush {
/**
* Creates an index from raw `ArrayBuffer` data.
* @param {ArrayBuffer} data
*/
static from(data) {
if (!(data instanceof ArrayBuffer)) {
throw new Error('Data must be an instance of ArrayBuffer.');
}
const [magic, versionAndType] = new Uint8Array(data, 0, 2);
if (magic !== 0xdb) {
throw new Error('Data does not appear to be in a KDBush format.');
}
const version = versionAndType >> 4;
if (version !== VERSION) {
throw new Error(`Got v${version} data when expected v${VERSION}.`);
}
const ArrayType = ARRAY_TYPES[versionAndType & 0x0f];
if (!ArrayType) {
throw new Error('Unrecognized array type.');
}
const [nodeSize] = new Uint16Array(data, 2, 1);
const [numItems] = new Uint32Array(data, 4, 1);
return new KDBush(numItems, nodeSize, ArrayType, data);
}
/**
* Creates an index that will hold a given number of items.
* @param {number} numItems
* @param {number} [nodeSize=64] Size of the KD-tree node (64 by default).
* @param {TypedArrayConstructor} [ArrayType=Float64Array] The array type used for coordinates storage (`Float64Array` by default).
* @param {ArrayBuffer} [data] (For internal use only)
*/
constructor(numItems, nodeSize = 64, ArrayType = Float64Array, data) {
if (isNaN(numItems) || numItems < 0) throw new Error(`Unpexpected numItems value: ${numItems}.`);
this.numItems = +numItems;
this.nodeSize = Math.min(Math.max(+nodeSize, 2), 65535);
this.ArrayType = ArrayType;
this.IndexArrayType = numItems < 65536 ? Uint16Array : Uint32Array;
const arrayTypeIndex = ARRAY_TYPES.indexOf(this.ArrayType);
const coordsByteSize = numItems * 2 * this.ArrayType.BYTES_PER_ELEMENT;
const idsByteSize = numItems * this.IndexArrayType.BYTES_PER_ELEMENT;
const padCoords = (8 - idsByteSize % 8) % 8;
if (arrayTypeIndex < 0) {
throw new Error(`Unexpected typed array class: ${ArrayType}.`);
}
if (data && (data instanceof ArrayBuffer)) { // reconstruct an index from a buffer
this.data = data;
this.ids = new this.IndexArrayType(this.data, HEADER_SIZE, numItems);
this.coords = new this.ArrayType(this.data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2);
this._pos = numItems * 2;
this._finished = true;
} else { // initialize a new index
this.data = new ArrayBuffer(HEADER_SIZE + coordsByteSize + idsByteSize + padCoords);
this.ids = new this.IndexArrayType(this.data, HEADER_SIZE, numItems);
this.coords = new this.ArrayType(this.data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2);
this._pos = 0;
this._finished = false;
// set header
new Uint8Array(this.data, 0, 2).set([0xdb, (VERSION << 4) + arrayTypeIndex]);
new Uint16Array(this.data, 2, 1)[0] = nodeSize;
new Uint32Array(this.data, 4, 1)[0] = numItems;
}
}
/**
* Add a point to the index.
* @param {number} x
* @param {number} y
* @returns {number} An incremental index associated with the added item (starting from `0`).
*/
add(x, y) {
const index = this._pos >> 1;
this.ids[index] = index;
this.coords[this._pos++] = x;
this.coords[this._pos++] = y;
return index;
}
/**
* Perform indexing of the added points.
*/
finish() {
const numAdded = this._pos >> 1;
if (numAdded !== this.numItems) {
throw new Error(`Added ${numAdded} items when expected ${this.numItems}.`);
}
// kd-sort both arrays for efficient search
sort(this.ids, this.coords, this.nodeSize, 0, this.numItems - 1, 0);
this._finished = true;
return this;
}
/**
* Search the index for items within a given bounding box.
* @param {number} minX
* @param {number} minY
* @param {number} maxX
* @param {number} maxY
* @returns {number[]} An array of indices correponding to the found items.
*/
range(minX, minY, maxX, maxY) {
if (!this._finished) throw new Error('Data not yet indexed - call index.finish().');
const {ids, coords, nodeSize} = this;
const stack = [0, ids.length - 1, 0];
const result = [];
// recursively search for items in range in the kd-sorted arrays
while (stack.length) {
const axis = stack.pop() || 0;
const right = stack.pop() || 0;
const left = stack.pop() || 0;
// if we reached "tree node", search linearly
if (right - left <= nodeSize) {
for (let i = left; i <= right; i++) {
const x = coords[2 * i];
const y = coords[2 * i + 1];
if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[i]);
}
continue;
}
// otherwise find the middle index
const m = (left + right) >> 1;
// include the middle item if it's in range
const x = coords[2 * m];
const y = coords[2 * m + 1];
if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[m]);
// queue search in halves that intersect the query
if (axis === 0 ? minX <= x : minY <= y) {
stack.push(left);
stack.push(m - 1);
stack.push(1 - axis);
}
if (axis === 0 ? maxX >= x : maxY >= y) {
stack.push(m + 1);
stack.push(right);
stack.push(1 - axis);
}
}
return result;
}
/**
* Search the index for items within a given radius.
* @param {number} qx
* @param {number} qy
* @param {number} r Query radius.
* @returns {number[]} An array of indices correponding to the found items.
*/
within(qx, qy, r) {
if (!this._finished) throw new Error('Data not yet indexed - call index.finish().');
const {ids, coords, nodeSize} = this;
const stack = [0, ids.length - 1, 0];
const result = [];
const r2 = r * r;
// recursively search for items within radius in the kd-sorted arrays
while (stack.length) {
const axis = stack.pop() || 0;
const right = stack.pop() || 0;
const left = stack.pop() || 0;
// if we reached "tree node", search linearly
if (right - left <= nodeSize) {
for (let i = left; i <= right; i++) {
if (sqDist(coords[2 * i], coords[2 * i + 1], qx, qy) <= r2) result.push(ids[i]);
}
continue;
}
// otherwise find the middle index
const m = (left + right) >> 1;
// include the middle item if it's in range
const x = coords[2 * m];
const y = coords[2 * m + 1];
if (sqDist(x, y, qx, qy) <= r2) result.push(ids[m]);
// queue search in halves that intersect the query
if (axis === 0 ? qx - r <= x : qy - r <= y) {
stack.push(left);
stack.push(m - 1);
stack.push(1 - axis);
}
if (axis === 0 ? qx + r >= x : qy + r >= y) {
stack.push(m + 1);
stack.push(right);
stack.push(1 - axis);
}
}
return result;
}
}
/**
* @param {Uint16Array | Uint32Array} ids
* @param {InstanceType<TypedArrayConstructor>} coords
* @param {number} nodeSize
* @param {number} left
* @param {number} right
* @param {number} axis
*/
function sort(ids, coords, nodeSize, left, right, axis) {
if (right - left <= nodeSize) return;
const m = (left + right) >> 1; // middle index
// sort ids and coords around the middle index so that the halves lie
// either left/right or top/bottom correspondingly (taking turns)
select(ids, coords, m, left, right, axis);
// recursively kd-sort first half and second half on the opposite axis
sort(ids, coords, nodeSize, left, m - 1, 1 - axis);
sort(ids, coords, nodeSize, m + 1, right, 1 - axis);
}
/**
* Custom Floyd-Rivest selection algorithm: sort ids and coords so that
* [left..k-1] items are smaller than k-th item (on either x or y axis)
* @param {Uint16Array | Uint32Array} ids
* @param {InstanceType<TypedArrayConstructor>} coords
* @param {number} k
* @param {number} left
* @param {number} right
* @param {number} axis
*/
function select(ids, coords, k, left, right, axis) {
while (right > left) {
if (right - left > 600) {
const n = right - left + 1;
const m = k - left + 1;
const z = Math.log(n);
const s = 0.5 * Math.exp(2 * z / 3);
const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
const newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
select(ids, coords, k, newLeft, newRight, axis);
}
const t = coords[2 * k + axis];
let i = left;
let j = right;
swapItem(ids, coords, left, k);
if (coords[2 * right + axis] > t) swapItem(ids, coords, left, right);
while (i < j) {
swapItem(ids, coords, i, j);
i++;
j--;
while (coords[2 * i + axis] < t) i++;
while (coords[2 * j + axis] > t) j--;
}
if (coords[2 * left + axis] === t) swapItem(ids, coords, left, j);
else {
j++;
swapItem(ids, coords, j, right);
}
if (j <= k) left = j + 1;
if (k <= j) right = j - 1;
}
}
/**
* @param {Uint16Array | Uint32Array} ids
* @param {InstanceType<TypedArrayConstructor>} coords
* @param {number} i
* @param {number} j
*/
function swapItem(ids, coords, i, j) {
swap(ids, i, j);
swap(coords, 2 * i, 2 * j);
swap(coords, 2 * i + 1, 2 * j + 1);
}
/**
* @param {InstanceType<TypedArrayConstructor>} arr
* @param {number} i
* @param {number} j
*/
function swap(arr, i, j) {
const tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
/**
* @param {number} ax
* @param {number} ay
* @param {number} bx
* @param {number} by
*/
function sqDist(ax, ay, bx, by) {
const dx = ax - bx;
const dy = ay - by;
return dx * dx + dy * dy;
}

336
src/treehug/node_modules/kdbush/kdbush.js generated vendored Normal file
View file

@ -0,0 +1,336 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.KDBush = factory());
})(this, (function () { 'use strict';
const ARRAY_TYPES = [
Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array,
Int32Array, Uint32Array, Float32Array, Float64Array
];
/** @typedef {Int8ArrayConstructor | Uint8ArrayConstructor | Uint8ClampedArrayConstructor | Int16ArrayConstructor | Uint16ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor} TypedArrayConstructor */
const VERSION = 1; // serialized format version
const HEADER_SIZE = 8;
class KDBush {
/**
* Creates an index from raw `ArrayBuffer` data.
* @param {ArrayBuffer} data
*/
static from(data) {
if (!(data instanceof ArrayBuffer)) {
throw new Error('Data must be an instance of ArrayBuffer.');
}
const [magic, versionAndType] = new Uint8Array(data, 0, 2);
if (magic !== 0xdb) {
throw new Error('Data does not appear to be in a KDBush format.');
}
const version = versionAndType >> 4;
if (version !== VERSION) {
throw new Error(`Got v${version} data when expected v${VERSION}.`);
}
const ArrayType = ARRAY_TYPES[versionAndType & 0x0f];
if (!ArrayType) {
throw new Error('Unrecognized array type.');
}
const [nodeSize] = new Uint16Array(data, 2, 1);
const [numItems] = new Uint32Array(data, 4, 1);
return new KDBush(numItems, nodeSize, ArrayType, data);
}
/**
* Creates an index that will hold a given number of items.
* @param {number} numItems
* @param {number} [nodeSize=64] Size of the KD-tree node (64 by default).
* @param {TypedArrayConstructor} [ArrayType=Float64Array] The array type used for coordinates storage (`Float64Array` by default).
* @param {ArrayBuffer} [data] (For internal use only)
*/
constructor(numItems, nodeSize = 64, ArrayType = Float64Array, data) {
if (isNaN(numItems) || numItems < 0) throw new Error(`Unpexpected numItems value: ${numItems}.`);
this.numItems = +numItems;
this.nodeSize = Math.min(Math.max(+nodeSize, 2), 65535);
this.ArrayType = ArrayType;
this.IndexArrayType = numItems < 65536 ? Uint16Array : Uint32Array;
const arrayTypeIndex = ARRAY_TYPES.indexOf(this.ArrayType);
const coordsByteSize = numItems * 2 * this.ArrayType.BYTES_PER_ELEMENT;
const idsByteSize = numItems * this.IndexArrayType.BYTES_PER_ELEMENT;
const padCoords = (8 - idsByteSize % 8) % 8;
if (arrayTypeIndex < 0) {
throw new Error(`Unexpected typed array class: ${ArrayType}.`);
}
if (data && (data instanceof ArrayBuffer)) { // reconstruct an index from a buffer
this.data = data;
this.ids = new this.IndexArrayType(this.data, HEADER_SIZE, numItems);
this.coords = new this.ArrayType(this.data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2);
this._pos = numItems * 2;
this._finished = true;
} else { // initialize a new index
this.data = new ArrayBuffer(HEADER_SIZE + coordsByteSize + idsByteSize + padCoords);
this.ids = new this.IndexArrayType(this.data, HEADER_SIZE, numItems);
this.coords = new this.ArrayType(this.data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2);
this._pos = 0;
this._finished = false;
// set header
new Uint8Array(this.data, 0, 2).set([0xdb, (VERSION << 4) + arrayTypeIndex]);
new Uint16Array(this.data, 2, 1)[0] = nodeSize;
new Uint32Array(this.data, 4, 1)[0] = numItems;
}
}
/**
* Add a point to the index.
* @param {number} x
* @param {number} y
* @returns {number} An incremental index associated with the added item (starting from `0`).
*/
add(x, y) {
const index = this._pos >> 1;
this.ids[index] = index;
this.coords[this._pos++] = x;
this.coords[this._pos++] = y;
return index;
}
/**
* Perform indexing of the added points.
*/
finish() {
const numAdded = this._pos >> 1;
if (numAdded !== this.numItems) {
throw new Error(`Added ${numAdded} items when expected ${this.numItems}.`);
}
// kd-sort both arrays for efficient search
sort(this.ids, this.coords, this.nodeSize, 0, this.numItems - 1, 0);
this._finished = true;
return this;
}
/**
* Search the index for items within a given bounding box.
* @param {number} minX
* @param {number} minY
* @param {number} maxX
* @param {number} maxY
* @returns {number[]} An array of indices correponding to the found items.
*/
range(minX, minY, maxX, maxY) {
if (!this._finished) throw new Error('Data not yet indexed - call index.finish().');
const {ids, coords, nodeSize} = this;
const stack = [0, ids.length - 1, 0];
const result = [];
// recursively search for items in range in the kd-sorted arrays
while (stack.length) {
const axis = stack.pop() || 0;
const right = stack.pop() || 0;
const left = stack.pop() || 0;
// if we reached "tree node", search linearly
if (right - left <= nodeSize) {
for (let i = left; i <= right; i++) {
const x = coords[2 * i];
const y = coords[2 * i + 1];
if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[i]);
}
continue;
}
// otherwise find the middle index
const m = (left + right) >> 1;
// include the middle item if it's in range
const x = coords[2 * m];
const y = coords[2 * m + 1];
if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[m]);
// queue search in halves that intersect the query
if (axis === 0 ? minX <= x : minY <= y) {
stack.push(left);
stack.push(m - 1);
stack.push(1 - axis);
}
if (axis === 0 ? maxX >= x : maxY >= y) {
stack.push(m + 1);
stack.push(right);
stack.push(1 - axis);
}
}
return result;
}
/**
* Search the index for items within a given radius.
* @param {number} qx
* @param {number} qy
* @param {number} r Query radius.
* @returns {number[]} An array of indices correponding to the found items.
*/
within(qx, qy, r) {
if (!this._finished) throw new Error('Data not yet indexed - call index.finish().');
const {ids, coords, nodeSize} = this;
const stack = [0, ids.length - 1, 0];
const result = [];
const r2 = r * r;
// recursively search for items within radius in the kd-sorted arrays
while (stack.length) {
const axis = stack.pop() || 0;
const right = stack.pop() || 0;
const left = stack.pop() || 0;
// if we reached "tree node", search linearly
if (right - left <= nodeSize) {
for (let i = left; i <= right; i++) {
if (sqDist(coords[2 * i], coords[2 * i + 1], qx, qy) <= r2) result.push(ids[i]);
}
continue;
}
// otherwise find the middle index
const m = (left + right) >> 1;
// include the middle item if it's in range
const x = coords[2 * m];
const y = coords[2 * m + 1];
if (sqDist(x, y, qx, qy) <= r2) result.push(ids[m]);
// queue search in halves that intersect the query
if (axis === 0 ? qx - r <= x : qy - r <= y) {
stack.push(left);
stack.push(m - 1);
stack.push(1 - axis);
}
if (axis === 0 ? qx + r >= x : qy + r >= y) {
stack.push(m + 1);
stack.push(right);
stack.push(1 - axis);
}
}
return result;
}
}
/**
* @param {Uint16Array | Uint32Array} ids
* @param {InstanceType<TypedArrayConstructor>} coords
* @param {number} nodeSize
* @param {number} left
* @param {number} right
* @param {number} axis
*/
function sort(ids, coords, nodeSize, left, right, axis) {
if (right - left <= nodeSize) return;
const m = (left + right) >> 1; // middle index
// sort ids and coords around the middle index so that the halves lie
// either left/right or top/bottom correspondingly (taking turns)
select(ids, coords, m, left, right, axis);
// recursively kd-sort first half and second half on the opposite axis
sort(ids, coords, nodeSize, left, m - 1, 1 - axis);
sort(ids, coords, nodeSize, m + 1, right, 1 - axis);
}
/**
* Custom Floyd-Rivest selection algorithm: sort ids and coords so that
* [left..k-1] items are smaller than k-th item (on either x or y axis)
* @param {Uint16Array | Uint32Array} ids
* @param {InstanceType<TypedArrayConstructor>} coords
* @param {number} k
* @param {number} left
* @param {number} right
* @param {number} axis
*/
function select(ids, coords, k, left, right, axis) {
while (right > left) {
if (right - left > 600) {
const n = right - left + 1;
const m = k - left + 1;
const z = Math.log(n);
const s = 0.5 * Math.exp(2 * z / 3);
const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
const newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
select(ids, coords, k, newLeft, newRight, axis);
}
const t = coords[2 * k + axis];
let i = left;
let j = right;
swapItem(ids, coords, left, k);
if (coords[2 * right + axis] > t) swapItem(ids, coords, left, right);
while (i < j) {
swapItem(ids, coords, i, j);
i++;
j--;
while (coords[2 * i + axis] < t) i++;
while (coords[2 * j + axis] > t) j--;
}
if (coords[2 * left + axis] === t) swapItem(ids, coords, left, j);
else {
j++;
swapItem(ids, coords, j, right);
}
if (j <= k) left = j + 1;
if (k <= j) right = j - 1;
}
}
/**
* @param {Uint16Array | Uint32Array} ids
* @param {InstanceType<TypedArrayConstructor>} coords
* @param {number} i
* @param {number} j
*/
function swapItem(ids, coords, i, j) {
swap(ids, i, j);
swap(coords, 2 * i, 2 * j);
swap(coords, 2 * i + 1, 2 * j + 1);
}
/**
* @param {InstanceType<TypedArrayConstructor>} arr
* @param {number} i
* @param {number} j
*/
function swap(arr, i, j) {
const tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
/**
* @param {number} ax
* @param {number} ay
* @param {number} bx
* @param {number} by
*/
function sqDist(ax, ay, bx, by) {
const dx = ax - bx;
const dy = ay - by;
return dx * dx + dy * dy;
}
return KDBush;
}));

1
src/treehug/node_modules/kdbush/kdbush.min.js generated vendored Normal file
View file

@ -0,0 +1 @@
!function(t,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(t="undefined"!=typeof globalThis?globalThis:t||self).KDBush=r()}(this,(function(){"use strict";const t=[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];class r{static from(n){if(!(n instanceof ArrayBuffer))throw new Error("Data must be an instance of ArrayBuffer.");const[s,e]=new Uint8Array(n,0,2);if(219!==s)throw new Error("Data does not appear to be in a KDBush format.");const i=e>>4;if(1!==i)throw new Error(`Got v${i} data when expected v1.`);const o=t[15&e];if(!o)throw new Error("Unrecognized array type.");const[h]=new Uint16Array(n,2,1),[a]=new Uint32Array(n,4,1);return new r(a,h,o,n)}constructor(r,n=64,s=Float64Array,e){if(isNaN(r)||r<0)throw new Error(`Unpexpected numItems value: ${r}.`);this.numItems=+r,this.nodeSize=Math.min(Math.max(+n,2),65535),this.ArrayType=s,this.IndexArrayType=r<65536?Uint16Array:Uint32Array;const i=t.indexOf(this.ArrayType),o=2*r*this.ArrayType.BYTES_PER_ELEMENT,h=r*this.IndexArrayType.BYTES_PER_ELEMENT,a=(8-h%8)%8;if(i<0)throw new Error(`Unexpected typed array class: ${s}.`);e&&e instanceof ArrayBuffer?(this.data=e,this.ids=new this.IndexArrayType(this.data,8,r),this.coords=new this.ArrayType(this.data,8+h+a,2*r),this._pos=2*r,this._finished=!0):(this.data=new ArrayBuffer(8+o+h+a),this.ids=new this.IndexArrayType(this.data,8,r),this.coords=new this.ArrayType(this.data,8+h+a,2*r),this._pos=0,this._finished=!1,new Uint8Array(this.data,0,2).set([219,16+i]),new Uint16Array(this.data,2,1)[0]=n,new Uint32Array(this.data,4,1)[0]=r)}add(t,r){const n=this._pos>>1;return this.ids[n]=n,this.coords[this._pos++]=t,this.coords[this._pos++]=r,n}finish(){const t=this._pos>>1;if(t!==this.numItems)throw new Error(`Added ${t} items when expected ${this.numItems}.`);return n(this.ids,this.coords,this.nodeSize,0,this.numItems-1,0),this._finished=!0,this}range(t,r,n,s){if(!this._finished)throw new Error("Data not yet indexed - call index.finish().");const{ids:e,coords:i,nodeSize:o}=this,h=[0,e.length-1,0],a=[];for(;h.length;){const d=h.pop()||0,f=h.pop()||0,p=h.pop()||0;if(f-p<=o){for(let o=p;o<=f;o++){const h=i[2*o],d=i[2*o+1];h>=t&&h<=n&&d>=r&&d<=s&&a.push(e[o])}continue}const c=p+f>>1,u=i[2*c],y=i[2*c+1];u>=t&&u<=n&&y>=r&&y<=s&&a.push(e[c]),(0===d?t<=u:r<=y)&&(h.push(p),h.push(c-1),h.push(1-d)),(0===d?n>=u:s>=y)&&(h.push(c+1),h.push(f),h.push(1-d))}return a}within(t,r,n){if(!this._finished)throw new Error("Data not yet indexed - call index.finish().");const{ids:s,coords:e,nodeSize:i}=this,h=[0,s.length-1,0],a=[],d=n*n;for(;h.length;){const f=h.pop()||0,p=h.pop()||0,c=h.pop()||0;if(p-c<=i){for(let n=c;n<=p;n++)o(e[2*n],e[2*n+1],t,r)<=d&&a.push(s[n]);continue}const u=c+p>>1,y=e[2*u],w=e[2*u+1];o(y,w,t,r)<=d&&a.push(s[u]),(0===f?t-n<=y:r-n<=w)&&(h.push(c),h.push(u-1),h.push(1-f)),(0===f?t+n>=y:r+n>=w)&&(h.push(u+1),h.push(p),h.push(1-f))}return a}}function n(t,r,e,i,o,h){if(o-i<=e)return;const a=i+o>>1;s(t,r,a,i,o,h),n(t,r,e,i,a-1,1-h),n(t,r,e,a+1,o,1-h)}function s(t,r,n,i,o,h){for(;o>i;){if(o-i>600){const e=o-i+1,a=n-i+1,d=Math.log(e),f=.5*Math.exp(2*d/3),p=.5*Math.sqrt(d*f*(e-f)/e)*(a-e/2<0?-1:1);s(t,r,n,Math.max(i,Math.floor(n-a*f/e+p)),Math.min(o,Math.floor(n+(e-a)*f/e+p)),h)}const a=r[2*n+h];let d=i,f=o;for(e(t,r,i,n),r[2*o+h]>a&&e(t,r,i,o);d<f;){for(e(t,r,d,f),d++,f--;r[2*d+h]<a;)d++;for(;r[2*f+h]>a;)f--}r[2*i+h]===a?e(t,r,i,f):(f++,e(t,r,f,o)),f<=n&&(i=f+1),n<=f&&(o=f-1)}}function e(t,r,n,s){i(t,n,s),i(r,2*n,2*s),i(r,2*n+1,2*s+1)}function i(t,r,n){const s=t[r];t[r]=t[n],t[n]=s}function o(t,r,n,s){const e=t-n,i=r-s;return e*e+i*i}return r}));

49
src/treehug/node_modules/kdbush/package.json generated vendored Normal file
View file

@ -0,0 +1,49 @@
{
"name": "kdbush",
"version": "4.0.2",
"description": "A very fast static 2D index for points based on kd-tree.",
"type": "module",
"main": "kdbush.js",
"module": "index.js",
"exports": "./index.js",
"types": "index.d.ts",
"sideEffects": false,
"repository": {
"type": "git",
"url": "git://github.com/mourner/kdbush.git"
},
"devDependencies": {
"@rollup/plugin-terser": "^0.4.1",
"eslint": "^8.38.0",
"eslint-config-mourner": "^3.0.0",
"rollup": "^3.20.6",
"typescript": "^5.0.4"
},
"scripts": {
"pretest": "eslint index.js test.js bench.js rollup.config.js",
"test": "tsc && node test.js",
"bench": "node bench.js",
"build": "rollup -c",
"prepublishOnly": "npm run test && npm run build"
},
"eslintConfig": {
"extends": "mourner"
},
"keywords": [
"index",
"points",
"kd-tree",
"data structures",
"algorithms",
"spatial",
"geometry"
],
"files": [
"kdbush.js",
"kdbush.min.js",
"index.js",
"index.d.ts"
],
"author": "Vladimir Agafonkin",
"license": "ISC"
}

15
src/treehug/node_modules/supercluster/LICENSE generated vendored Normal file
View file

@ -0,0 +1,15 @@
ISC License
Copyright (c) 2021, Mapbox
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

112
src/treehug/node_modules/supercluster/README.md generated vendored Normal file
View file

@ -0,0 +1,112 @@
# supercluster [![Simply Awesome](https://img.shields.io/badge/simply-awesome-brightgreen.svg)](https://github.com/mourner/projects) [![Build Status](https://travis-ci.com/mapbox/supercluster.svg?branch=main)](https://travis-ci.com/mapbox/supercluster)
A very fast JavaScript library for geospatial point clustering for browsers and Node.
```js
const index = new Supercluster({radius: 40, maxZoom: 16});
index.load(points);
const clusters = index.getClusters([-180, -85, 180, 85], 2);
```
Clustering 6 million points in Leaflet:
![clustering demo on an interactive Leaflet map](https://cloud.githubusercontent.com/assets/25395/11857351/43407b46-a40c-11e5-8662-e99ab1cd2cb7.gif)
Supercluster was built to power clustering in [Mapbox GL JS](https://www.mapbox.com/mapbox-gljs). Read about how it works [on the Mapbox blog](https://blog.mapbox.com/clustering-millions-of-points-on-a-map-with-supercluster-272046ec5c97).
## Install
Install using NPM (`npm install supercluster`) or Yarn (`yarn add supercluster`), then:
```js
// import as a ES module in Node
import Supercluster from 'supercluster';
// import from a CDN in the browser:
import Supercluster from 'https://esm.run/supercluster';
```
Or use it with an ordinary script tag in the browser:
```html
<script src="https://unpkg.com/supercluster@8.0.0/dist/supercluster.min.js"></script>
```
## Methods
#### `load(points)`
Loads an array of [GeoJSON Feature](https://tools.ietf.org/html/rfc7946#section-3.2) objects. Each feature's `geometry` must be a [GeoJSON Point](https://tools.ietf.org/html/rfc7946#section-3.1.2). Once loaded, index is immutable.
#### `getClusters(bbox, zoom)`
For the given `bbox` array (`[westLng, southLat, eastLng, northLat]`) and integer `zoom`, returns an array of clusters and points as [GeoJSON Feature](https://tools.ietf.org/html/rfc7946#section-3.2) objects.
#### `getTile(z, x, y)`
For a given zoom and x/y coordinates, returns a [geojson-vt](https://github.com/mapbox/geojson-vt)-compatible JSON tile object with cluster/point features.
#### `getChildren(clusterId)`
Returns the children of a cluster (on the next zoom level) given its id (`cluster_id` value from feature properties).
#### `getLeaves(clusterId, limit = 10, offset = 0)`
Returns all the points of a cluster (given its `cluster_id`), with pagination support:
`limit` is the number of points to return (set to `Infinity` for all points),
and `offset` is the amount of points to skip (for pagination).
#### `getClusterExpansionZoom(clusterId)`
Returns the zoom on which the cluster expands into several children (useful for "click to zoom" feature) given the cluster's `cluster_id`.
## Options
| Option | Default | Description |
|------------|---------|-------------------------------------------------------------------|
| minZoom | 0 | Minimum zoom level at which clusters are generated. |
| maxZoom | 16 | Maximum zoom level at which clusters are generated. |
| minPoints | 2 | Minimum number of points to form a cluster. |
| radius | 40 | Cluster radius, in pixels. |
| extent | 512 | (Tiles) Tile extent. Radius is calculated relative to this value. |
| nodeSize | 64 | Size of the KD-tree leaf node. Affects performance. |
| log | false | Whether timing info should be logged. |
| generateId | false | Whether to generate ids for input features in vector tiles. |
### Property map/reduce options
In addition to the options above, Supercluster supports property aggregation with the following two options:
- `map`: a function that returns cluster properties corresponding to a single point.
- `reduce`: a reduce function that merges properties of two clusters into one.
Example of setting up a `sum` cluster property that accumulates the sum of `myValue` property values:
```js
const index = new Supercluster({
map: (props) => ({sum: props.myValue}),
reduce: (accumulated, props) => { accumulated.sum += props.sum; }
});
```
The `map`/`reduce` options must satisfy these conditions to work correctly:
- `map` must return a new object, not existing `properties` of a point, otherwise it will get overwritten.
- `reduce` must not mutate the second argument (`props`).
## TypeScript
Install `@types/supercluster` for the TypeScript type definitions:
```
npm install @types/supercluster --save-dev
```
## Developing Supercluster
```
npm install # install dependencies
npm run build # generate dist/supercluster.js and dist/supercluster.min.js
npm test # run tests
```

View file

@ -0,0 +1,758 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Supercluster = factory());
})(this, (function () { 'use strict';
const ARRAY_TYPES = [
Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array,
Int32Array, Uint32Array, Float32Array, Float64Array
];
/** @typedef {Int8ArrayConstructor | Uint8ArrayConstructor | Uint8ClampedArrayConstructor | Int16ArrayConstructor | Uint16ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor} TypedArrayConstructor */
const VERSION = 1; // serialized format version
const HEADER_SIZE = 8;
class KDBush {
/**
* Creates an index from raw `ArrayBuffer` data.
* @param {ArrayBuffer} data
*/
static from(data) {
if (!(data instanceof ArrayBuffer)) {
throw new Error('Data must be an instance of ArrayBuffer.');
}
const [magic, versionAndType] = new Uint8Array(data, 0, 2);
if (magic !== 0xdb) {
throw new Error('Data does not appear to be in a KDBush format.');
}
const version = versionAndType >> 4;
if (version !== VERSION) {
throw new Error(`Got v${version} data when expected v${VERSION}.`);
}
const ArrayType = ARRAY_TYPES[versionAndType & 0x0f];
if (!ArrayType) {
throw new Error('Unrecognized array type.');
}
const [nodeSize] = new Uint16Array(data, 2, 1);
const [numItems] = new Uint32Array(data, 4, 1);
return new KDBush(numItems, nodeSize, ArrayType, data);
}
/**
* Creates an index that will hold a given number of items.
* @param {number} numItems
* @param {number} [nodeSize=64] Size of the KD-tree node (64 by default).
* @param {TypedArrayConstructor} [ArrayType=Float64Array] The array type used for coordinates storage (`Float64Array` by default).
* @param {ArrayBuffer} [data] (For internal use only)
*/
constructor(numItems, nodeSize = 64, ArrayType = Float64Array, data) {
if (isNaN(numItems) || numItems < 0) throw new Error(`Unpexpected numItems value: ${numItems}.`);
this.numItems = +numItems;
this.nodeSize = Math.min(Math.max(+nodeSize, 2), 65535);
this.ArrayType = ArrayType;
this.IndexArrayType = numItems < 65536 ? Uint16Array : Uint32Array;
const arrayTypeIndex = ARRAY_TYPES.indexOf(this.ArrayType);
const coordsByteSize = numItems * 2 * this.ArrayType.BYTES_PER_ELEMENT;
const idsByteSize = numItems * this.IndexArrayType.BYTES_PER_ELEMENT;
const padCoords = (8 - idsByteSize % 8) % 8;
if (arrayTypeIndex < 0) {
throw new Error(`Unexpected typed array class: ${ArrayType}.`);
}
if (data && (data instanceof ArrayBuffer)) { // reconstruct an index from a buffer
this.data = data;
this.ids = new this.IndexArrayType(this.data, HEADER_SIZE, numItems);
this.coords = new this.ArrayType(this.data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2);
this._pos = numItems * 2;
this._finished = true;
} else { // initialize a new index
this.data = new ArrayBuffer(HEADER_SIZE + coordsByteSize + idsByteSize + padCoords);
this.ids = new this.IndexArrayType(this.data, HEADER_SIZE, numItems);
this.coords = new this.ArrayType(this.data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2);
this._pos = 0;
this._finished = false;
// set header
new Uint8Array(this.data, 0, 2).set([0xdb, (VERSION << 4) + arrayTypeIndex]);
new Uint16Array(this.data, 2, 1)[0] = nodeSize;
new Uint32Array(this.data, 4, 1)[0] = numItems;
}
}
/**
* Add a point to the index.
* @param {number} x
* @param {number} y
* @returns {number} An incremental index associated with the added item (starting from `0`).
*/
add(x, y) {
const index = this._pos >> 1;
this.ids[index] = index;
this.coords[this._pos++] = x;
this.coords[this._pos++] = y;
return index;
}
/**
* Perform indexing of the added points.
*/
finish() {
const numAdded = this._pos >> 1;
if (numAdded !== this.numItems) {
throw new Error(`Added ${numAdded} items when expected ${this.numItems}.`);
}
// kd-sort both arrays for efficient search
sort(this.ids, this.coords, this.nodeSize, 0, this.numItems - 1, 0);
this._finished = true;
return this;
}
/**
* Search the index for items within a given bounding box.
* @param {number} minX
* @param {number} minY
* @param {number} maxX
* @param {number} maxY
* @returns {number[]} An array of indices correponding to the found items.
*/
range(minX, minY, maxX, maxY) {
if (!this._finished) throw new Error('Data not yet indexed - call index.finish().');
const {ids, coords, nodeSize} = this;
const stack = [0, ids.length - 1, 0];
const result = [];
// recursively search for items in range in the kd-sorted arrays
while (stack.length) {
const axis = stack.pop() || 0;
const right = stack.pop() || 0;
const left = stack.pop() || 0;
// if we reached "tree node", search linearly
if (right - left <= nodeSize) {
for (let i = left; i <= right; i++) {
const x = coords[2 * i];
const y = coords[2 * i + 1];
if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[i]);
}
continue;
}
// otherwise find the middle index
const m = (left + right) >> 1;
// include the middle item if it's in range
const x = coords[2 * m];
const y = coords[2 * m + 1];
if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[m]);
// queue search in halves that intersect the query
if (axis === 0 ? minX <= x : minY <= y) {
stack.push(left);
stack.push(m - 1);
stack.push(1 - axis);
}
if (axis === 0 ? maxX >= x : maxY >= y) {
stack.push(m + 1);
stack.push(right);
stack.push(1 - axis);
}
}
return result;
}
/**
* Search the index for items within a given radius.
* @param {number} qx
* @param {number} qy
* @param {number} r Query radius.
* @returns {number[]} An array of indices correponding to the found items.
*/
within(qx, qy, r) {
if (!this._finished) throw new Error('Data not yet indexed - call index.finish().');
const {ids, coords, nodeSize} = this;
const stack = [0, ids.length - 1, 0];
const result = [];
const r2 = r * r;
// recursively search for items within radius in the kd-sorted arrays
while (stack.length) {
const axis = stack.pop() || 0;
const right = stack.pop() || 0;
const left = stack.pop() || 0;
// if we reached "tree node", search linearly
if (right - left <= nodeSize) {
for (let i = left; i <= right; i++) {
if (sqDist(coords[2 * i], coords[2 * i + 1], qx, qy) <= r2) result.push(ids[i]);
}
continue;
}
// otherwise find the middle index
const m = (left + right) >> 1;
// include the middle item if it's in range
const x = coords[2 * m];
const y = coords[2 * m + 1];
if (sqDist(x, y, qx, qy) <= r2) result.push(ids[m]);
// queue search in halves that intersect the query
if (axis === 0 ? qx - r <= x : qy - r <= y) {
stack.push(left);
stack.push(m - 1);
stack.push(1 - axis);
}
if (axis === 0 ? qx + r >= x : qy + r >= y) {
stack.push(m + 1);
stack.push(right);
stack.push(1 - axis);
}
}
return result;
}
}
/**
* @param {Uint16Array | Uint32Array} ids
* @param {InstanceType<TypedArrayConstructor>} coords
* @param {number} nodeSize
* @param {number} left
* @param {number} right
* @param {number} axis
*/
function sort(ids, coords, nodeSize, left, right, axis) {
if (right - left <= nodeSize) return;
const m = (left + right) >> 1; // middle index
// sort ids and coords around the middle index so that the halves lie
// either left/right or top/bottom correspondingly (taking turns)
select(ids, coords, m, left, right, axis);
// recursively kd-sort first half and second half on the opposite axis
sort(ids, coords, nodeSize, left, m - 1, 1 - axis);
sort(ids, coords, nodeSize, m + 1, right, 1 - axis);
}
/**
* Custom Floyd-Rivest selection algorithm: sort ids and coords so that
* [left..k-1] items are smaller than k-th item (on either x or y axis)
* @param {Uint16Array | Uint32Array} ids
* @param {InstanceType<TypedArrayConstructor>} coords
* @param {number} k
* @param {number} left
* @param {number} right
* @param {number} axis
*/
function select(ids, coords, k, left, right, axis) {
while (right > left) {
if (right - left > 600) {
const n = right - left + 1;
const m = k - left + 1;
const z = Math.log(n);
const s = 0.5 * Math.exp(2 * z / 3);
const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
const newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
select(ids, coords, k, newLeft, newRight, axis);
}
const t = coords[2 * k + axis];
let i = left;
let j = right;
swapItem(ids, coords, left, k);
if (coords[2 * right + axis] > t) swapItem(ids, coords, left, right);
while (i < j) {
swapItem(ids, coords, i, j);
i++;
j--;
while (coords[2 * i + axis] < t) i++;
while (coords[2 * j + axis] > t) j--;
}
if (coords[2 * left + axis] === t) swapItem(ids, coords, left, j);
else {
j++;
swapItem(ids, coords, j, right);
}
if (j <= k) left = j + 1;
if (k <= j) right = j - 1;
}
}
/**
* @param {Uint16Array | Uint32Array} ids
* @param {InstanceType<TypedArrayConstructor>} coords
* @param {number} i
* @param {number} j
*/
function swapItem(ids, coords, i, j) {
swap(ids, i, j);
swap(coords, 2 * i, 2 * j);
swap(coords, 2 * i + 1, 2 * j + 1);
}
/**
* @param {InstanceType<TypedArrayConstructor>} arr
* @param {number} i
* @param {number} j
*/
function swap(arr, i, j) {
const tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
/**
* @param {number} ax
* @param {number} ay
* @param {number} bx
* @param {number} by
*/
function sqDist(ax, ay, bx, by) {
const dx = ax - bx;
const dy = ay - by;
return dx * dx + dy * dy;
}
const defaultOptions = {
minZoom: 0, // min zoom to generate clusters on
maxZoom: 16, // max zoom level to cluster the points on
minPoints: 2, // minimum points to form a cluster
radius: 40, // cluster radius in pixels
extent: 512, // tile extent (radius is calculated relative to it)
nodeSize: 64, // size of the KD-tree leaf node, affects performance
log: false, // whether to log timing info
// whether to generate numeric ids for input features (in vector tiles)
generateId: false,
// a reduce function for calculating custom cluster properties
reduce: null, // (accumulated, props) => { accumulated.sum += props.sum; }
// properties to use for individual points when running the reducer
map: props => props // props => ({sum: props.my_value})
};
const fround = Math.fround || (tmp => ((x) => { tmp[0] = +x; return tmp[0]; }))(new Float32Array(1));
const OFFSET_ZOOM = 2;
const OFFSET_ID = 3;
const OFFSET_PARENT = 4;
const OFFSET_NUM = 5;
const OFFSET_PROP = 6;
class Supercluster {
constructor(options) {
this.options = Object.assign(Object.create(defaultOptions), options);
this.trees = new Array(this.options.maxZoom + 1);
this.stride = this.options.reduce ? 7 : 6;
this.clusterProps = [];
}
load(points) {
const {log, minZoom, maxZoom} = this.options;
if (log) console.time('total time');
const timerId = `prepare ${ points.length } points`;
if (log) console.time(timerId);
this.points = points;
// generate a cluster object for each point and index input points into a KD-tree
const data = [];
for (let i = 0; i < points.length; i++) {
const p = points[i];
if (!p.geometry) continue;
const [lng, lat] = p.geometry.coordinates;
const x = fround(lngX(lng));
const y = fround(latY(lat));
// store internal point/cluster data in flat numeric arrays for performance
data.push(
x, y, // projected point coordinates
Infinity, // the last zoom the point was processed at
i, // index of the source feature in the original input array
-1, // parent cluster id
1 // number of points in a cluster
);
if (this.options.reduce) data.push(0); // noop
}
let tree = this.trees[maxZoom + 1] = this._createTree(data);
if (log) console.timeEnd(timerId);
// cluster points on max zoom, then cluster the results on previous zoom, etc.;
// results in a cluster hierarchy across zoom levels
for (let z = maxZoom; z >= minZoom; z--) {
const now = +Date.now();
// create a new set of clusters for the zoom and index them with a KD-tree
tree = this.trees[z] = this._createTree(this._cluster(tree, z));
if (log) console.log('z%d: %d clusters in %dms', z, tree.numItems, +Date.now() - now);
}
if (log) console.timeEnd('total time');
return this;
}
getClusters(bbox, zoom) {
let minLng = ((bbox[0] + 180) % 360 + 360) % 360 - 180;
const minLat = Math.max(-90, Math.min(90, bbox[1]));
let maxLng = bbox[2] === 180 ? 180 : ((bbox[2] + 180) % 360 + 360) % 360 - 180;
const maxLat = Math.max(-90, Math.min(90, bbox[3]));
if (bbox[2] - bbox[0] >= 360) {
minLng = -180;
maxLng = 180;
} else if (minLng > maxLng) {
const easternHem = this.getClusters([minLng, minLat, 180, maxLat], zoom);
const westernHem = this.getClusters([-180, minLat, maxLng, maxLat], zoom);
return easternHem.concat(westernHem);
}
const tree = this.trees[this._limitZoom(zoom)];
const ids = tree.range(lngX(minLng), latY(maxLat), lngX(maxLng), latY(minLat));
const data = tree.data;
const clusters = [];
for (const id of ids) {
const k = this.stride * id;
clusters.push(data[k + OFFSET_NUM] > 1 ? getClusterJSON(data, k, this.clusterProps) : this.points[data[k + OFFSET_ID]]);
}
return clusters;
}
getChildren(clusterId) {
const originId = this._getOriginId(clusterId);
const originZoom = this._getOriginZoom(clusterId);
const errorMsg = 'No cluster with the specified id.';
const tree = this.trees[originZoom];
if (!tree) throw new Error(errorMsg);
const data = tree.data;
if (originId * this.stride >= data.length) throw new Error(errorMsg);
const r = this.options.radius / (this.options.extent * Math.pow(2, originZoom - 1));
const x = data[originId * this.stride];
const y = data[originId * this.stride + 1];
const ids = tree.within(x, y, r);
const children = [];
for (const id of ids) {
const k = id * this.stride;
if (data[k + OFFSET_PARENT] === clusterId) {
children.push(data[k + OFFSET_NUM] > 1 ? getClusterJSON(data, k, this.clusterProps) : this.points[data[k + OFFSET_ID]]);
}
}
if (children.length === 0) throw new Error(errorMsg);
return children;
}
getLeaves(clusterId, limit, offset) {
limit = limit || 10;
offset = offset || 0;
const leaves = [];
this._appendLeaves(leaves, clusterId, limit, offset, 0);
return leaves;
}
getTile(z, x, y) {
const tree = this.trees[this._limitZoom(z)];
const z2 = Math.pow(2, z);
const {extent, radius} = this.options;
const p = radius / extent;
const top = (y - p) / z2;
const bottom = (y + 1 + p) / z2;
const tile = {
features: []
};
this._addTileFeatures(
tree.range((x - p) / z2, top, (x + 1 + p) / z2, bottom),
tree.data, x, y, z2, tile);
if (x === 0) {
this._addTileFeatures(
tree.range(1 - p / z2, top, 1, bottom),
tree.data, z2, y, z2, tile);
}
if (x === z2 - 1) {
this._addTileFeatures(
tree.range(0, top, p / z2, bottom),
tree.data, -1, y, z2, tile);
}
return tile.features.length ? tile : null;
}
getClusterExpansionZoom(clusterId) {
let expansionZoom = this._getOriginZoom(clusterId) - 1;
while (expansionZoom <= this.options.maxZoom) {
const children = this.getChildren(clusterId);
expansionZoom++;
if (children.length !== 1) break;
clusterId = children[0].properties.cluster_id;
}
return expansionZoom;
}
_appendLeaves(result, clusterId, limit, offset, skipped) {
const children = this.getChildren(clusterId);
for (const child of children) {
const props = child.properties;
if (props && props.cluster) {
if (skipped + props.point_count <= offset) {
// skip the whole cluster
skipped += props.point_count;
} else {
// enter the cluster
skipped = this._appendLeaves(result, props.cluster_id, limit, offset, skipped);
// exit the cluster
}
} else if (skipped < offset) {
// skip a single point
skipped++;
} else {
// add a single point
result.push(child);
}
if (result.length === limit) break;
}
return skipped;
}
_createTree(data) {
const tree = new KDBush(data.length / this.stride | 0, this.options.nodeSize, Float32Array);
for (let i = 0; i < data.length; i += this.stride) tree.add(data[i], data[i + 1]);
tree.finish();
tree.data = data;
return tree;
}
_addTileFeatures(ids, data, x, y, z2, tile) {
for (const i of ids) {
const k = i * this.stride;
const isCluster = data[k + OFFSET_NUM] > 1;
let tags, px, py;
if (isCluster) {
tags = getClusterProperties(data, k, this.clusterProps);
px = data[k];
py = data[k + 1];
} else {
const p = this.points[data[k + OFFSET_ID]];
tags = p.properties;
const [lng, lat] = p.geometry.coordinates;
px = lngX(lng);
py = latY(lat);
}
const f = {
type: 1,
geometry: [[
Math.round(this.options.extent * (px * z2 - x)),
Math.round(this.options.extent * (py * z2 - y))
]],
tags
};
// assign id
let id;
if (isCluster || this.options.generateId) {
// optionally generate id for points
id = data[k + OFFSET_ID];
} else {
// keep id if already assigned
id = this.points[data[k + OFFSET_ID]].id;
}
if (id !== undefined) f.id = id;
tile.features.push(f);
}
}
_limitZoom(z) {
return Math.max(this.options.minZoom, Math.min(Math.floor(+z), this.options.maxZoom + 1));
}
_cluster(tree, zoom) {
const {radius, extent, reduce, minPoints} = this.options;
const r = radius / (extent * Math.pow(2, zoom));
const data = tree.data;
const nextData = [];
const stride = this.stride;
// loop through each point
for (let i = 0; i < data.length; i += stride) {
// if we've already visited the point at this zoom level, skip it
if (data[i + OFFSET_ZOOM] <= zoom) continue;
data[i + OFFSET_ZOOM] = zoom;
// find all nearby points
const x = data[i];
const y = data[i + 1];
const neighborIds = tree.within(data[i], data[i + 1], r);
const numPointsOrigin = data[i + OFFSET_NUM];
let numPoints = numPointsOrigin;
// count the number of points in a potential cluster
for (const neighborId of neighborIds) {
const k = neighborId * stride;
// filter out neighbors that are already processed
if (data[k + OFFSET_ZOOM] > zoom) numPoints += data[k + OFFSET_NUM];
}
// if there were neighbors to merge, and there are enough points to form a cluster
if (numPoints > numPointsOrigin && numPoints >= minPoints) {
let wx = x * numPointsOrigin;
let wy = y * numPointsOrigin;
let clusterProperties;
let clusterPropIndex = -1;
// encode both zoom and point index on which the cluster originated -- offset by total length of features
const id = ((i / stride | 0) << 5) + (zoom + 1) + this.points.length;
for (const neighborId of neighborIds) {
const k = neighborId * stride;
if (data[k + OFFSET_ZOOM] <= zoom) continue;
data[k + OFFSET_ZOOM] = zoom; // save the zoom (so it doesn't get processed twice)
const numPoints2 = data[k + OFFSET_NUM];
wx += data[k] * numPoints2; // accumulate coordinates for calculating weighted center
wy += data[k + 1] * numPoints2;
data[k + OFFSET_PARENT] = id;
if (reduce) {
if (!clusterProperties) {
clusterProperties = this._map(data, i, true);
clusterPropIndex = this.clusterProps.length;
this.clusterProps.push(clusterProperties);
}
reduce(clusterProperties, this._map(data, k));
}
}
data[i + OFFSET_PARENT] = id;
nextData.push(wx / numPoints, wy / numPoints, Infinity, id, -1, numPoints);
if (reduce) nextData.push(clusterPropIndex);
} else { // left points as unclustered
for (let j = 0; j < stride; j++) nextData.push(data[i + j]);
if (numPoints > 1) {
for (const neighborId of neighborIds) {
const k = neighborId * stride;
if (data[k + OFFSET_ZOOM] <= zoom) continue;
data[k + OFFSET_ZOOM] = zoom;
for (let j = 0; j < stride; j++) nextData.push(data[k + j]);
}
}
}
}
return nextData;
}
// get index of the point from which the cluster originated
_getOriginId(clusterId) {
return (clusterId - this.points.length) >> 5;
}
// get zoom of the point from which the cluster originated
_getOriginZoom(clusterId) {
return (clusterId - this.points.length) % 32;
}
_map(data, i, clone) {
if (data[i + OFFSET_NUM] > 1) {
const props = this.clusterProps[data[i + OFFSET_PROP]];
return clone ? Object.assign({}, props) : props;
}
const original = this.points[data[i + OFFSET_ID]].properties;
const result = this.options.map(original);
return clone && result === original ? Object.assign({}, result) : result;
}
}
function getClusterJSON(data, i, clusterProps) {
return {
type: 'Feature',
id: data[i + OFFSET_ID],
properties: getClusterProperties(data, i, clusterProps),
geometry: {
type: 'Point',
coordinates: [xLng(data[i]), yLat(data[i + 1])]
}
};
}
function getClusterProperties(data, i, clusterProps) {
const count = data[i + OFFSET_NUM];
const abbrev =
count >= 10000 ? `${Math.round(count / 1000) }k` :
count >= 1000 ? `${Math.round(count / 100) / 10 }k` : count;
const propIndex = data[i + OFFSET_PROP];
const properties = propIndex === -1 ? {} : Object.assign({}, clusterProps[propIndex]);
return Object.assign(properties, {
cluster: true,
cluster_id: data[i + OFFSET_ID],
point_count: count,
point_count_abbreviated: abbrev
});
}
// longitude/latitude to spherical mercator in [0..1] range
function lngX(lng) {
return lng / 360 + 0.5;
}
function latY(lat) {
const sin = Math.sin(lat * Math.PI / 180);
const y = (0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI);
return y < 0 ? 0 : y > 1 ? 1 : y;
}
// spherical mercator to longitude/latitude
function xLng(x) {
return (x - 0.5) * 360;
}
function yLat(y) {
const y2 = (180 - y * 360) * Math.PI / 180;
return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
}
return Supercluster;
}));

File diff suppressed because one or more lines are too long

424
src/treehug/node_modules/supercluster/index.js generated vendored Normal file
View file

@ -0,0 +1,424 @@
import KDBush from 'kdbush';
const defaultOptions = {
minZoom: 0, // min zoom to generate clusters on
maxZoom: 16, // max zoom level to cluster the points on
minPoints: 2, // minimum points to form a cluster
radius: 40, // cluster radius in pixels
extent: 512, // tile extent (radius is calculated relative to it)
nodeSize: 64, // size of the KD-tree leaf node, affects performance
log: false, // whether to log timing info
// whether to generate numeric ids for input features (in vector tiles)
generateId: false,
// a reduce function for calculating custom cluster properties
reduce: null, // (accumulated, props) => { accumulated.sum += props.sum; }
// properties to use for individual points when running the reducer
map: props => props // props => ({sum: props.my_value})
};
const fround = Math.fround || (tmp => ((x) => { tmp[0] = +x; return tmp[0]; }))(new Float32Array(1));
const OFFSET_ZOOM = 2;
const OFFSET_ID = 3;
const OFFSET_PARENT = 4;
const OFFSET_NUM = 5;
const OFFSET_PROP = 6;
export default class Supercluster {
constructor(options) {
this.options = Object.assign(Object.create(defaultOptions), options);
this.trees = new Array(this.options.maxZoom + 1);
this.stride = this.options.reduce ? 7 : 6;
this.clusterProps = [];
}
load(points) {
const {log, minZoom, maxZoom} = this.options;
if (log) console.time('total time');
const timerId = `prepare ${ points.length } points`;
if (log) console.time(timerId);
this.points = points;
// generate a cluster object for each point and index input points into a KD-tree
const data = [];
for (let i = 0; i < points.length; i++) {
const p = points[i];
if (!p.geometry) continue;
const [lng, lat] = p.geometry.coordinates;
const x = fround(lngX(lng));
const y = fround(latY(lat));
// store internal point/cluster data in flat numeric arrays for performance
data.push(
x, y, // projected point coordinates
Infinity, // the last zoom the point was processed at
i, // index of the source feature in the original input array
-1, // parent cluster id
1 // number of points in a cluster
);
if (this.options.reduce) data.push(0); // noop
}
let tree = this.trees[maxZoom + 1] = this._createTree(data);
if (log) console.timeEnd(timerId);
// cluster points on max zoom, then cluster the results on previous zoom, etc.;
// results in a cluster hierarchy across zoom levels
for (let z = maxZoom; z >= minZoom; z--) {
const now = +Date.now();
// create a new set of clusters for the zoom and index them with a KD-tree
tree = this.trees[z] = this._createTree(this._cluster(tree, z));
if (log) console.log('z%d: %d clusters in %dms', z, tree.numItems, +Date.now() - now);
}
if (log) console.timeEnd('total time');
return this;
}
getClusters(bbox, zoom) {
let minLng = ((bbox[0] + 180) % 360 + 360) % 360 - 180;
const minLat = Math.max(-90, Math.min(90, bbox[1]));
let maxLng = bbox[2] === 180 ? 180 : ((bbox[2] + 180) % 360 + 360) % 360 - 180;
const maxLat = Math.max(-90, Math.min(90, bbox[3]));
if (bbox[2] - bbox[0] >= 360) {
minLng = -180;
maxLng = 180;
} else if (minLng > maxLng) {
const easternHem = this.getClusters([minLng, minLat, 180, maxLat], zoom);
const westernHem = this.getClusters([-180, minLat, maxLng, maxLat], zoom);
return easternHem.concat(westernHem);
}
const tree = this.trees[this._limitZoom(zoom)];
const ids = tree.range(lngX(minLng), latY(maxLat), lngX(maxLng), latY(minLat));
const data = tree.data;
const clusters = [];
for (const id of ids) {
const k = this.stride * id;
clusters.push(data[k + OFFSET_NUM] > 1 ? getClusterJSON(data, k, this.clusterProps) : this.points[data[k + OFFSET_ID]]);
}
return clusters;
}
getChildren(clusterId) {
const originId = this._getOriginId(clusterId);
const originZoom = this._getOriginZoom(clusterId);
const errorMsg = 'No cluster with the specified id.';
const tree = this.trees[originZoom];
if (!tree) throw new Error(errorMsg);
const data = tree.data;
if (originId * this.stride >= data.length) throw new Error(errorMsg);
const r = this.options.radius / (this.options.extent * Math.pow(2, originZoom - 1));
const x = data[originId * this.stride];
const y = data[originId * this.stride + 1];
const ids = tree.within(x, y, r);
const children = [];
for (const id of ids) {
const k = id * this.stride;
if (data[k + OFFSET_PARENT] === clusterId) {
children.push(data[k + OFFSET_NUM] > 1 ? getClusterJSON(data, k, this.clusterProps) : this.points[data[k + OFFSET_ID]]);
}
}
if (children.length === 0) throw new Error(errorMsg);
return children;
}
getLeaves(clusterId, limit, offset) {
limit = limit || 10;
offset = offset || 0;
const leaves = [];
this._appendLeaves(leaves, clusterId, limit, offset, 0);
return leaves;
}
getTile(z, x, y) {
const tree = this.trees[this._limitZoom(z)];
const z2 = Math.pow(2, z);
const {extent, radius} = this.options;
const p = radius / extent;
const top = (y - p) / z2;
const bottom = (y + 1 + p) / z2;
const tile = {
features: []
};
this._addTileFeatures(
tree.range((x - p) / z2, top, (x + 1 + p) / z2, bottom),
tree.data, x, y, z2, tile);
if (x === 0) {
this._addTileFeatures(
tree.range(1 - p / z2, top, 1, bottom),
tree.data, z2, y, z2, tile);
}
if (x === z2 - 1) {
this._addTileFeatures(
tree.range(0, top, p / z2, bottom),
tree.data, -1, y, z2, tile);
}
return tile.features.length ? tile : null;
}
getClusterExpansionZoom(clusterId) {
let expansionZoom = this._getOriginZoom(clusterId) - 1;
while (expansionZoom <= this.options.maxZoom) {
const children = this.getChildren(clusterId);
expansionZoom++;
if (children.length !== 1) break;
clusterId = children[0].properties.cluster_id;
}
return expansionZoom;
}
_appendLeaves(result, clusterId, limit, offset, skipped) {
const children = this.getChildren(clusterId);
for (const child of children) {
const props = child.properties;
if (props && props.cluster) {
if (skipped + props.point_count <= offset) {
// skip the whole cluster
skipped += props.point_count;
} else {
// enter the cluster
skipped = this._appendLeaves(result, props.cluster_id, limit, offset, skipped);
// exit the cluster
}
} else if (skipped < offset) {
// skip a single point
skipped++;
} else {
// add a single point
result.push(child);
}
if (result.length === limit) break;
}
return skipped;
}
_createTree(data) {
const tree = new KDBush(data.length / this.stride | 0, this.options.nodeSize, Float32Array);
for (let i = 0; i < data.length; i += this.stride) tree.add(data[i], data[i + 1]);
tree.finish();
tree.data = data;
return tree;
}
_addTileFeatures(ids, data, x, y, z2, tile) {
for (const i of ids) {
const k = i * this.stride;
const isCluster = data[k + OFFSET_NUM] > 1;
let tags, px, py;
if (isCluster) {
tags = getClusterProperties(data, k, this.clusterProps);
px = data[k];
py = data[k + 1];
} else {
const p = this.points[data[k + OFFSET_ID]];
tags = p.properties;
const [lng, lat] = p.geometry.coordinates;
px = lngX(lng);
py = latY(lat);
}
const f = {
type: 1,
geometry: [[
Math.round(this.options.extent * (px * z2 - x)),
Math.round(this.options.extent * (py * z2 - y))
]],
tags
};
// assign id
let id;
if (isCluster || this.options.generateId) {
// optionally generate id for points
id = data[k + OFFSET_ID];
} else {
// keep id if already assigned
id = this.points[data[k + OFFSET_ID]].id;
}
if (id !== undefined) f.id = id;
tile.features.push(f);
}
}
_limitZoom(z) {
return Math.max(this.options.minZoom, Math.min(Math.floor(+z), this.options.maxZoom + 1));
}
_cluster(tree, zoom) {
const {radius, extent, reduce, minPoints} = this.options;
const r = radius / (extent * Math.pow(2, zoom));
const data = tree.data;
const nextData = [];
const stride = this.stride;
// loop through each point
for (let i = 0; i < data.length; i += stride) {
// if we've already visited the point at this zoom level, skip it
if (data[i + OFFSET_ZOOM] <= zoom) continue;
data[i + OFFSET_ZOOM] = zoom;
// find all nearby points
const x = data[i];
const y = data[i + 1];
const neighborIds = tree.within(data[i], data[i + 1], r);
const numPointsOrigin = data[i + OFFSET_NUM];
let numPoints = numPointsOrigin;
// count the number of points in a potential cluster
for (const neighborId of neighborIds) {
const k = neighborId * stride;
// filter out neighbors that are already processed
if (data[k + OFFSET_ZOOM] > zoom) numPoints += data[k + OFFSET_NUM];
}
// if there were neighbors to merge, and there are enough points to form a cluster
if (numPoints > numPointsOrigin && numPoints >= minPoints) {
let wx = x * numPointsOrigin;
let wy = y * numPointsOrigin;
let clusterProperties;
let clusterPropIndex = -1;
// encode both zoom and point index on which the cluster originated -- offset by total length of features
const id = ((i / stride | 0) << 5) + (zoom + 1) + this.points.length;
for (const neighborId of neighborIds) {
const k = neighborId * stride;
if (data[k + OFFSET_ZOOM] <= zoom) continue;
data[k + OFFSET_ZOOM] = zoom; // save the zoom (so it doesn't get processed twice)
const numPoints2 = data[k + OFFSET_NUM];
wx += data[k] * numPoints2; // accumulate coordinates for calculating weighted center
wy += data[k + 1] * numPoints2;
data[k + OFFSET_PARENT] = id;
if (reduce) {
if (!clusterProperties) {
clusterProperties = this._map(data, i, true);
clusterPropIndex = this.clusterProps.length;
this.clusterProps.push(clusterProperties);
}
reduce(clusterProperties, this._map(data, k));
}
}
data[i + OFFSET_PARENT] = id;
nextData.push(wx / numPoints, wy / numPoints, Infinity, id, -1, numPoints);
if (reduce) nextData.push(clusterPropIndex);
} else { // left points as unclustered
for (let j = 0; j < stride; j++) nextData.push(data[i + j]);
if (numPoints > 1) {
for (const neighborId of neighborIds) {
const k = neighborId * stride;
if (data[k + OFFSET_ZOOM] <= zoom) continue;
data[k + OFFSET_ZOOM] = zoom;
for (let j = 0; j < stride; j++) nextData.push(data[k + j]);
}
}
}
}
return nextData;
}
// get index of the point from which the cluster originated
_getOriginId(clusterId) {
return (clusterId - this.points.length) >> 5;
}
// get zoom of the point from which the cluster originated
_getOriginZoom(clusterId) {
return (clusterId - this.points.length) % 32;
}
_map(data, i, clone) {
if (data[i + OFFSET_NUM] > 1) {
const props = this.clusterProps[data[i + OFFSET_PROP]];
return clone ? Object.assign({}, props) : props;
}
const original = this.points[data[i + OFFSET_ID]].properties;
const result = this.options.map(original);
return clone && result === original ? Object.assign({}, result) : result;
}
}
function getClusterJSON(data, i, clusterProps) {
return {
type: 'Feature',
id: data[i + OFFSET_ID],
properties: getClusterProperties(data, i, clusterProps),
geometry: {
type: 'Point',
coordinates: [xLng(data[i]), yLat(data[i + 1])]
}
};
}
function getClusterProperties(data, i, clusterProps) {
const count = data[i + OFFSET_NUM];
const abbrev =
count >= 10000 ? `${Math.round(count / 1000) }k` :
count >= 1000 ? `${Math.round(count / 100) / 10 }k` : count;
const propIndex = data[i + OFFSET_PROP];
const properties = propIndex === -1 ? {} : Object.assign({}, clusterProps[propIndex]);
return Object.assign(properties, {
cluster: true,
cluster_id: data[i + OFFSET_ID],
point_count: count,
point_count_abbreviated: abbrev
});
}
// longitude/latitude to spherical mercator in [0..1] range
function lngX(lng) {
return lng / 360 + 0.5;
}
function latY(lat) {
const sin = Math.sin(lat * Math.PI / 180);
const y = (0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI);
return y < 0 ? 0 : y > 1 ? 1 : y;
}
// spherical mercator to longitude/latitude
function xLng(x) {
return (x - 0.5) * 360;
}
function yLat(y) {
const y2 = (180 - y * 360) * Math.PI / 180;
return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
}

57
src/treehug/node_modules/supercluster/package.json generated vendored Normal file
View file

@ -0,0 +1,57 @@
{
"name": "supercluster",
"version": "8.0.1",
"description": "A very fast geospatial point clustering library.",
"main": "dist/supercluster.js",
"type": "module",
"exports": "./index.js",
"module": "index.js",
"jsdelivr": "dist/supercluster.min.js",
"unpkg": "dist/supercluster.min.js",
"sideEffects": false,
"scripts": {
"pretest": "eslint index.js bench.js test/test.js demo/index.js demo/worker.js",
"test": "node test/test.js",
"cov": "c8 npm run test",
"bench": "node --expose-gc bench.js",
"build": "mkdirp dist && rollup -c",
"prepublishOnly": "npm run test && npm run build"
},
"files": [
"index.js",
"dist/supercluster.js",
"dist/supercluster.min.js"
],
"repository": {
"type": "git",
"url": "git://github.com/mapbox/supercluster.git"
},
"keywords": [
"clustering",
"geospatial",
"markers"
],
"author": "Vladimir Agafonkin",
"license": "ISC",
"dependencies": {
"kdbush": "^4.0.2"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.0.2",
"@rollup/plugin-terser": "^0.4.1",
"c8": "^7.13.0",
"eslint": "^8.39.0",
"eslint-config-mourner": "^3.0.0",
"mkdirp": "^3.0.1",
"rollup": "^3.21.0"
},
"eslintConfig": {
"extends": "mourner",
"parserOptions": {
"ecmaVersion": 2020
},
"rules": {
"camelcase": 0
}
}
}

48
src/treehug/package-lock.json generated Normal file
View file

@ -0,0 +1,48 @@
{
"name": "TreeHug",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@googlemaps/markerclusterer": "^2.5.0"
},
"devDependencies": {
"@types/google.maps": "^3.54.6"
}
},
"node_modules/@googlemaps/markerclusterer": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.5.0.tgz",
"integrity": "sha512-WpHLCZxP7QmB4Hc5kyODGdTfJPsZiOIbcvbYhcS/VeiRNDVjf6CRQ8ViQjwrG5OySC66rtOdj4RVhUXsd1tNTQ==",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"supercluster": "^8.0.1"
}
},
"node_modules/@types/google.maps": {
"version": "3.54.6",
"resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.54.6.tgz",
"integrity": "sha512-cTGbsddDgEwZ/6xPywI7HKbeYJkfXFg92HdYnlE0HO3gT/030CVdUHLO2uQOkEo0YMp6TRPqTlAnRGNbQJu8vw==",
"dev": true
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/kdbush": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
"integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="
},
"node_modules/supercluster": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
"integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
"dependencies": {
"kdbush": "^4.0.2"
}
}
}
}

8
src/treehug/package.json Normal file
View file

@ -0,0 +1,8 @@
{
"devDependencies": {
"@types/google.maps": "^3.54.6"
},
"dependencies": {
"@googlemaps/markerclusterer": "^2.5.0"
}
}

22
src/treehug/style.css Normal file
View file

@ -0,0 +1,22 @@
/*
* Always set the map height explicitly to define the size of the div element
* that contains the map.
*/
#map {
height: 100%;
width: 100%;
}
/*
* Optional: Makes the sample page fill the window.
*/
body {
margin: 0;
padding: 2%;
height: 90%;
}
html {
height: 100%;
}

109
src/treehug/tsconfig.json Normal file
View file

@ -0,0 +1,109 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"lib": ["es2015.promise", "es5", "dom"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "es2015", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}