Added TreeHug
This commit is contained in:
parent
50a9332cb8
commit
2ffe913059
136
src/treehug/frmWrk/.gitignore
vendored
Normal file
136
src/treehug/frmWrk/.gitignore
vendored
Normal 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/
|
62
src/treehug/frmWrk/README.MD
Normal file
62
src/treehug/frmWrk/README.MD
Normal 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.
|
||||||
|
|
2
src/treehug/frmWrk/__init__.py
Normal file
2
src/treehug/frmWrk/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import frmWrk.website
|
||||||
|
import frmWrk.decorators
|
192
src/treehug/frmWrk/databases.py
Normal file
192
src/treehug/frmWrk/databases.py
Normal 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"
|
||||||
|
|
||||||
|
|
32
src/treehug/frmWrk/decorators.py
Normal file
32
src/treehug/frmWrk/decorators.py
Normal 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
src/treehug/frmWrk/favicon.ico
Normal file
BIN
src/treehug/frmWrk/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
55
src/treehug/frmWrk/memorialForTheLostLog.txt
Normal file
55
src/treehug/frmWrk/memorialForTheLostLog.txt
Normal file
File diff suppressed because one or more lines are too long
300
src/treehug/frmWrk/website.py
Normal file
300
src/treehug/frmWrk/website.py
Normal 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
18
src/treehug/index.html
Normal 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
164
src/treehug/index.js
Normal 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
109
src/treehug/index.ts
Normal 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
4
src/treehug/main.py
Normal 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
40
src/treehug/node_modules/.package-lock.json
generated
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
202
src/treehug/node_modules/@googlemaps/markerclusterer/LICENSE
generated
vendored
Normal file
202
src/treehug/node_modules/@googlemaps/markerclusterer/LICENSE
generated
vendored
Normal 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.
|
105
src/treehug/node_modules/@googlemaps/markerclusterer/README.md
generated
vendored
Normal file
105
src/treehug/node_modules/@googlemaps/markerclusterer/README.md
generated
vendored
Normal 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
|
113
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/core.d.ts
generated
vendored
Normal file
113
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/core.d.ts
generated
vendored
Normal 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[];
|
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/core.test.d.ts
generated
vendored
Normal file
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/core.test.d.ts
generated
vendored
Normal 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 {};
|
46
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/grid.d.ts
generated
vendored
Normal file
46
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/grid.d.ts
generated
vendored
Normal 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;
|
||||||
|
}
|
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/grid.test.d.ts
generated
vendored
Normal file
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/grid.test.d.ts
generated
vendored
Normal 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 {};
|
21
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/index.d.ts
generated
vendored
Normal file
21
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/index.d.ts
generated
vendored
Normal 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";
|
25
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/noop.d.ts
generated
vendored
Normal file
25
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/noop.d.ts
generated
vendored
Normal 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[];
|
||||||
|
}
|
43
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/supercluster.d.ts
generated
vendored
Normal file
43
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/supercluster.d.ts
generated
vendored
Normal 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;
|
||||||
|
}
|
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/supercluster.test.d.ts
generated
vendored
Normal file
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/supercluster.test.d.ts
generated
vendored
Normal 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 {};
|
43
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/superviewport.d.ts
generated
vendored
Normal file
43
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/superviewport.d.ts
generated
vendored
Normal 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;
|
||||||
|
}
|
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/superviewport.test.d.ts
generated
vendored
Normal file
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/superviewport.test.d.ts
generated
vendored
Normal 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 {};
|
56
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/utils.d.ts
generated
vendored
Normal file
56
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/utils.d.ts
generated
vendored
Normal 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 {};
|
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/utils.test.d.ts
generated
vendored
Normal file
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/algorithms/utils.test.d.ts
generated
vendored
Normal 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 {};
|
41
src/treehug/node_modules/@googlemaps/markerclusterer/dist/cluster.d.ts
generated
vendored
Normal file
41
src/treehug/node_modules/@googlemaps/markerclusterer/dist/cluster.d.ts
generated
vendored
Normal 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;
|
||||||
|
}
|
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/cluster.test.d.ts
generated
vendored
Normal file
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/cluster.test.d.ts
generated
vendored
Normal 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 {};
|
20
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.d.ts
generated
vendored
Normal file
20
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.d.ts
generated
vendored
Normal 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";
|
5279
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.dev.js
generated
vendored
Normal file
5279
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.dev.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
956
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.esm.js
generated
vendored
Normal file
956
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.esm.js
generated
vendored
Normal 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
|
1
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.esm.js.map
generated
vendored
Normal file
1
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.esm.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.min.js
generated
vendored
Normal file
2
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.min.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.min.js.map
generated
vendored
Normal file
1
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.min.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.umd.js
generated
vendored
Normal file
2
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.umd.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.umd.js.map
generated
vendored
Normal file
1
src/treehug/node_modules/@googlemaps/markerclusterer/dist/index.umd.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
31
src/treehug/node_modules/@googlemaps/markerclusterer/dist/marker-utils.d.ts
generated
vendored
Normal file
31
src/treehug/node_modules/@googlemaps/markerclusterer/dist/marker-utils.d.ts
generated
vendored
Normal 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;
|
||||||
|
}
|
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/marker-utils.test.d.ts
generated
vendored
Normal file
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/marker-utils.test.d.ts
generated
vendored
Normal 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 {};
|
77
src/treehug/node_modules/@googlemaps/markerclusterer/dist/markerclusterer.d.ts
generated
vendored
Normal file
77
src/treehug/node_modules/@googlemaps/markerclusterer/dist/markerclusterer.d.ts
generated
vendored
Normal 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;
|
||||||
|
}
|
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/markerclusterer.test.d.ts
generated
vendored
Normal file
16
src/treehug/node_modules/@googlemaps/markerclusterer/dist/markerclusterer.test.d.ts
generated
vendored
Normal 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 {};
|
24
src/treehug/node_modules/@googlemaps/markerclusterer/dist/overlay-view-safe.d.ts
generated
vendored
Normal file
24
src/treehug/node_modules/@googlemaps/markerclusterer/dist/overlay-view-safe.d.ts
generated
vendored
Normal 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();
|
||||||
|
}
|
92
src/treehug/node_modules/@googlemaps/markerclusterer/dist/renderer.d.ts
generated
vendored
Normal file
92
src/treehug/node_modules/@googlemaps/markerclusterer/dist/renderer.d.ts
generated
vendored
Normal 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;
|
||||||
|
}
|
80
src/treehug/node_modules/@googlemaps/markerclusterer/package.json
generated
vendored
Normal file
80
src/treehug/node_modules/@googlemaps/markerclusterer/package.json
generated
vendored
Normal 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
21
src/treehug/node_modules/@types/google.maps/LICENSE
generated
vendored
Normal 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
15
src/treehug/node_modules/@types/google.maps/README.md
generated
vendored
Normal 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
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
31
src/treehug/node_modules/@types/google.maps/package.json
generated
vendored
Normal file
31
src/treehug/node_modules/@types/google.maps/package.json
generated
vendored
Normal 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
21
src/treehug/node_modules/fast-deep-equal/LICENSE
generated
vendored
Normal 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
96
src/treehug/node_modules/fast-deep-equal/README.md
generated
vendored
Normal 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)
|
2
src/treehug/node_modules/fast-deep-equal/es6/index.d.ts
generated
vendored
Normal file
2
src/treehug/node_modules/fast-deep-equal/es6/index.d.ts
generated
vendored
Normal 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
72
src/treehug/node_modules/fast-deep-equal/es6/index.js
generated
vendored
Normal 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;
|
||||||
|
};
|
2
src/treehug/node_modules/fast-deep-equal/es6/react.d.ts
generated
vendored
Normal file
2
src/treehug/node_modules/fast-deep-equal/es6/react.d.ts
generated
vendored
Normal 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
79
src/treehug/node_modules/fast-deep-equal/es6/react.js
generated
vendored
Normal 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
4
src/treehug/node_modules/fast-deep-equal/index.d.ts
generated
vendored
Normal 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
46
src/treehug/node_modules/fast-deep-equal/index.js
generated
vendored
Normal 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
61
src/treehug/node_modules/fast-deep-equal/package.json
generated
vendored
Normal 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
2
src/treehug/node_modules/fast-deep-equal/react.d.ts
generated
vendored
Normal 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
53
src/treehug/node_modules/fast-deep-equal/react.js
generated
vendored
Normal 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
15
src/treehug/node_modules/kdbush/LICENSE
generated
vendored
Normal 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
102
src/treehug/node_modules/kdbush/README.md
generated
vendored
Normal 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
53
src/treehug/node_modules/kdbush/index.d.ts
generated
vendored
Normal 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
327
src/treehug/node_modules/kdbush/index.js
generated
vendored
Normal 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
336
src/treehug/node_modules/kdbush/kdbush.js
generated
vendored
Normal 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
1
src/treehug/node_modules/kdbush/kdbush.min.js
generated
vendored
Normal 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
49
src/treehug/node_modules/kdbush/package.json
generated
vendored
Normal 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
15
src/treehug/node_modules/supercluster/LICENSE
generated
vendored
Normal 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
112
src/treehug/node_modules/supercluster/README.md
generated
vendored
Normal 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
|
||||||
|
```
|
758
src/treehug/node_modules/supercluster/dist/supercluster.js
generated
vendored
Normal file
758
src/treehug/node_modules/supercluster/dist/supercluster.js
generated
vendored
Normal 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;
|
||||||
|
|
||||||
|
}));
|
1
src/treehug/node_modules/supercluster/dist/supercluster.min.js
generated
vendored
Normal file
1
src/treehug/node_modules/supercluster/dist/supercluster.min.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
424
src/treehug/node_modules/supercluster/index.js
generated
vendored
Normal file
424
src/treehug/node_modules/supercluster/index.js
generated
vendored
Normal 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
57
src/treehug/node_modules/supercluster/package.json
generated
vendored
Normal 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
48
src/treehug/package-lock.json
generated
Normal 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
8
src/treehug/package.json
Normal 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
22
src/treehug/style.css
Normal 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
109
src/treehug/tsconfig.json
Normal 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. */
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue