diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f658c3c8..32f244f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,42 +4,56 @@ on: push: branches: - master + jobs: - test: + + lint: runs-on: ubuntu-latest - services: - postgres: - image: postgres:latest - env: - POSTGRES_DB: django - POSTGRES_PASSWORD: postgres - ports: - - 5432:5432 - rabbitmq: - image: rabbitmq:management - ports: - - 5672:5672 - minio: - # This image does not require any command arguments (which GitHub Actions don't support) - image: bitnami/minio:latest - env: - MINIO_ROOT_USER: minioAccessKey - MINIO_ROOT_PASSWORD: minioSecretKey - ports: - - 9000:9000 steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - run: pip install tox + - name: Lint + run: tox -e lint + + lint-client: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - run: cd web && npm i && npm run lint + + pytest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: adambirds/docker-compose-action@v1.3.0 + with: + compose-file: | + docker-compose.yml + docker-compose.override.yml + services: | + django + celery + postgres + minio + rabbitmq + test-container: django + test-command: "tox -e test" + + check-migrations: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: adambirds/docker-compose-action@v1.3.0 with: - python-version: "3.10" - - name: Build docker image - run: docker build -t test -f dev/django.Dockerfile . - - name: Run tests - run: | - docker run test tox - env: - DJANGO_DATABASE_URL: postgres://postgres:postgres@localhost:5432/django - DJANGO_MINIO_STORAGE_ENDPOINT: localhost:9000 - DJANGO_MINIO_STORAGE_ACCESS_KEY: minioAccessKey - DJANGO_MINIO_STORAGE_SECRET_KEY: minioSecretKey + compose-file: | + docker-compose.yml + docker-compose.override.yml + services: | + django + celery + postgres + minio + rabbitmq + test-container: django + test-command: "tox -e check-migrations" diff --git a/.gitignore b/.gitignore index 5f564667..1f5b09ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ .terraform/ +postgres/* +minio/* +rabbitmq/* .vscode/ staticfiles/ -sample_data/**/* +sample_data/*/* # Created by https://www.gitignore.io/api/django # Edit at https://www.gitignore.io/?templates=django diff --git a/dev/django.Dockerfile b/dev/django.Dockerfile index 30cf5fc1..ae6dc4c2 100644 --- a/dev/django.Dockerfile +++ b/dev/django.Dockerfile @@ -3,7 +3,7 @@ FROM python:3.10-slim # * psycopg2 RUN apt-get update && \ apt-get install --no-install-recommends --yes \ - libpq-dev libvips-dev gcc libc6-dev gdal-bin && \ + libpq-dev libvips-dev gcc libc6-dev gdal-bin git && \ rm -rf /var/lib/apt/lists/* ENV PYTHONDONTWRITEBYTECODE 1 diff --git a/sample_data/charts.json b/sample_data/charts.json index 87956ff7..9de31636 100644 --- a/sample_data/charts.json +++ b/sample_data/charts.json @@ -2,11 +2,14 @@ { "name": "Boston Harbor Daily Tide Levels", "description": "Raw data was obtained using the NOAA CO-OPS API for Data Retrieval and reformatted in tabular form", - "category": "ocean", - "city": "Boston, MA", - "raw_data_type": "csv", - "url": "https://data.kitware.com/api/v1/item/64beb508b4d956782eee8cb1/download", - "path": "boston/tide_level_data.csv", + "context": "Boston Transportation", + "files": [ + { + "url": "https://data.kitware.com/api/v1/item/64beb508b4d956782eee8cb1/download", + "path": "boston/tide_level_data.csv" + } + ], + "editable": false, "metadata": { "columns": { "Date": "YYYY-MM-DD", @@ -36,23 +39,19 @@ "x_title": "Date Recorded", "y_title": "Distance from Sensor (m)" }, - "style": { - "options": { - "chart": { - "labels": "Date", - "datasets": [ - "HH", - "H", - "L", - "LL" - ] - }, - "palette": { - "HH": "red", - "H": "pink", - "L": "lightblue", - "LL": "blue" - } + "conversion_options": { + "labels": "Date", + "datasets": [ + "HH", + "H", + "L", + "LL" + ], + "palette": { + "HH": "red", + "H": "pink", + "L": "lightblue", + "LL": "blue" } } } diff --git a/sample_data/contexts.json b/sample_data/contexts.json new file mode 100644 index 00000000..0f6428b2 --- /dev/null +++ b/sample_data/contexts.json @@ -0,0 +1,56 @@ +[ + { + "name": "Boston Transportation", + "default_map_center": [ + 42.4, + -71.1 + ], + "default_map_zoom": 11, + "datasets": [ + "MBTA Rapid Transit", + "MBTA Commuter Rail", + "Massachusetts Elevation Data", + "Boston Hurricane Surge Inundation Zones", + "Bsoton FEMA National Flood Hazard", + "Boston Neighborhoods", + "Boston Census 2020 Block Groups", + "Boston Zip Codes", + "Boston Sea Level Rises", + "Boston 10-Year Flood Events", + "Boston 100-Year Flood Events" + ] + }, + { + "name": "DC Transportation", + "default_map_center": [ + 38.9, + -77.1 + ], + "default_map_zoom": 11, + "datasets": [ + "DC Metro" + ] + }, + { + "name": "Boston-Washington Transportation", + "default_map_center": [ + 40.5, + -74.5 + ], + "default_map_zoom": 8, + "datasets": [ + "MBTA Rapid Transit", + "MBTA Commuter Rail", + "Massachusetts Elevation Data", + "Boston Hurricane Surge Inundation Zones", + "Bsoton FEMA National Flood Hazard", + "Boston Neighborhoods", + "Boston Census 2020 Block Groups", + "Boston Zip Codes", + "Boston Sea Level Rises", + "Boston 10-Year Flood Events", + "Boston 100-Year Flood Events", + "DC Metro" + ] + } +] diff --git a/sample_data/datasets.json b/sample_data/datasets.json index 51f65e48..7dd34202 100644 --- a/sample_data/datasets.json +++ b/sample_data/datasets.json @@ -3,94 +3,88 @@ "name": "MBTA Rapid Transit", "description": "Boston Subway System Lines and Stops", "category": "transportation", - "city": "Boston, MA", - "raw_data_type": "shape_file_archive", - "url": "https://data.kitware.com/api/v1/item/64b95be1cf6a0eaef6c141f6/download", - "path": "boston/mbta_rapid_transit.zip", - "network": true, - "metadata": { + "type": "vector", + "files": [ + { + "url": "https://data.kitware.com/api/v1/item/64b95be1cf6a0eaef6c141f6/download", + "path": "boston/mbta_rapid_transit.zip" + } + ], + "network_options": { "connection_column": "ROUTE", "connection_column_delimiter": "/", "node_id_column": "STATION" }, - "style": { - "property_map": { - "colors": "LINE" - }, - "options": { - "color_delimiter": "/", - "outline": "white" - } + "style_options": { + "color_property": "LINE", + "color_delimiter": "/", + "outline": "white" } }, { "name": "MBTA Commuter Rail", "description": "Rail linework and station points for passenger, freight, and Amtrak and MBTA Commuter Rail trains.", "category": "transportation", - "city": "Boston, MA", - "raw_data_type": "shape_file_archive", - "url": "https://data.kitware.com/api/v1/item/64907c77f04fb368544295ed/download", - "path": "boston/commuter_rail.zip", - "network": true, - "metadata": { + "type": "vector", + "files": [ + { + "url": "https://data.kitware.com/api/v1/item/64907c77f04fb368544295ed/download", + "path": "boston/commuter_rail.zip" + } + ], + "network_options": { "connection_column": "LINE_BRNCH", "connection_column_delimiter": "/", "node_id_column": "STATION" }, - "style": { - "property_map": { - "colors": "gray" - }, - "options": { - "outline": "black" - } + "style_options": { + "color_property": "gray", + "outline": "black" } }, { - "name": "Hurricane Surge Inundation Zones", + "name": "Boston Hurricane Surge Inundation Zones", "description": "This layer represents worst-case Hurricane Surge Inundation areas for Category 1 through 4 hurricanes striking the coast of Massachusetts.", "category": "climate", - "city": "Boston, MA", - "raw_data_type": "shape_file_archive", - "url": "https://data.kitware.com/api/v1/item/64907b6ef04fb368544295e7/download", - "path": "boston/hurr_inun.zip", - "style": { - "property_map": { - "colors": "HURR_CAT" - }, - "options": { - "palette": { - "1": "powderblue", - "2": "deepskyblue", - "3": "royalblue", - "4": "mediumblue", - "5": "darkblue" - } + "type": "vector", + "files": [ + { + "url": "https://data.kitware.com/api/v1/item/64907b6ef04fb368544295e7/download", + "path": "boston/hurr_inun.zip" + } + ], + "style_options": { + "color_property": "HURR_CAT", + "palette": { + "1": "powderblue", + "2": "deepskyblue", + "3": "royalblue", + "4": "mediumblue", + "5": "darkblue" } } }, { - "name": "FEMA National Flood Hazard", + "name": "Boston FEMA National Flood Hazard", "description": "The National Flood Hazard Layer (NFHL) dataset represents the current effective flood risk data for those parts of the country where maps have been modernized by the Federal Emergency Management Agency (FEMA).", "category": "climate", - "city": "Boston, MA", - "raw_data_type": "shape_file_archive", - "url": "https://data.kitware.com/api/v1/item/64907beff04fb368544295ea/download", - "path": "boston/flood_hazard_fema.zip", - "style": { - "property_map": { - "colors": "FLD_ZONE" - }, - "options": { - "palette": { - "A": "gold", - "AE": "orange", - "AH": "tomato", - "AO": "orangered", - "VE": "darkred", - "D": "gray", - "X": "green" - } + "type": "vector", + "files": [ + { + "url": "https://data.kitware.com/api/v1/item/64907beff04fb368544295ea/download", + "path": "boston/flood_hazard_fema.zip" + } + ], + "style_options": { + "color_property": "FLD_ZONE", + "palette": { + "A": "gold", + "AE": "orange", + "AH": "tomato", + "AO": "orangered", + "VE": "darkred", + "D": "gray", + "X": "green" } } }, @@ -98,249 +92,178 @@ "name": "Massachusetts Elevation Data", "description": "Low resolution sampling of land elevation in meters above sea level", "category": "elevation", - "city": "Boston, MA", - "raw_data_type": "cloud_optimized_geotiff", - "url": "https://data.kitware.com/api/v1/item/64907e4df04fb368544295f0/download", - "path": "boston/easternmass.tif", - "style": { - "options": { - "transparency_threshold": 1, - "trim_distribution_percentage": 0.01 + "type": "raster", + "files": [ + { + "url": "https://data.kitware.com/api/v1/item/64907e4df04fb368544295f0/download", + "path": "boston/easternmass.tif" } + ], + "style_options": { + "transparency_threshold": 1, + "trim_distribution_percentage": 0.01 } }, { "name": "Boston Neighborhoods", "description": "The Neighborhood boundaries data layer is a combination of zoning neighborhood boundaries, zip code boundaries and 2010 Census tract boundaries", "category": "region", - "city": "Boston, MA", - "raw_data_type": "geojson", - "url": "https://data.kitware.com/api/v1/item/64caa3da77edef4e1ea8ef57/download", - "path": "boston/neighborhoods2020.json", - "style": { - "property_map": { - "name": "BlockGr202" - }, - "options": { - "outline": "white", - "palette": [ - "blue" - ] + "type": "vector", + "files": [ + { + "url": "https://data.kitware.com/api/v1/item/64caa3da77edef4e1ea8ef57/download", + "path": "boston/neighborhoods2020.json" } + ], + "region_options": { + "name_property": "BlockGr202" + }, + "style_options": { + "outline": "white", + "palette": [ + "blue" + ] } }, { "name": "Boston Census 2020 Block Groups", "description": "Block groups (between 600 and 3,000 people per block) for Boston from the 2020 census", "category": "region", - "city": "Boston, MA", - "raw_data_type": "shape_file_archive", - "url": "https://data.boston.gov/dataset/c478b600-3e3e-46fd-9f57-da89459e9928/resource/11282722-9386-4272-8a82-2fcec89e6d55/download/census2020_blockgroups.zip", - "path": "mass/blockgroups.json", - "style": { - "property_map": { - "name": "GEOID20" - }, - "options": { - "outline": "white", - "palette": [ - "green" - ] + "type": "vector", + "files": [ + { + "url": "https://data.boston.gov/dataset/c478b600-3e3e-46fd-9f57-da89459e9928/resource/11282722-9386-4272-8a82-2fcec89e6d55/download/census2020_blockgroups.zip", + "path": "mass/blockgroups.zip" } + ], + "region_options": { + "name_property": "GEOID20" + }, + "style_options": { + "outline": "white", + "palette": [ + "green" + ] } }, { "name": "Boston Zip Codes", "description": "Zip codes 01001-02791", "category": "region", - "city": "Boston, MA", - "raw_data_type": "shape_file_archive", - "url": "https://data.kitware.com/api/v1/item/64fbb4c6e99e9e6006f00114/download", - "path": "boston/zipcodes.json", - "style": { - "options": { - "outline": "white", - "palette": [ - "grey" - ] - } - } - }, - { - "name": "9-inch Sea Level Rise", - "description": "From Analyze Boston; 9 inches of sea level rise is expected across emissions scenarios as likely to occur as early as the 2030s.", - "category": "flood", - "city": "Boston, MA", - "raw_data_type": "geojson", - "url": "https://data.kitware.com/api/v1/item/64f8743d6725af35134c1479/download", - "path": "boston/9in_rise.geojson", - "style": { - "options": { - "outline": "white", - "palette": [ - "blue" - ] - } - } - }, - { - "name": "9-inch 10-year Flood", - "description": "From Analyze Boston; a 9-inch flood event that has a 1 in 10 chance of occurring in any given year.", - "category": "flood", - "city": "Boston, MA", - "raw_data_type": "geojson", - "url": "https://data.kitware.com/api/v1/item/64f87ba86725af35134c1485/download", - "path": "boston/9in_10yr_flood.geojson", - "style": { - "options": { - "outline": "white", - "palette": [ - "blue" - ] - } - } - }, - { - "name": "9-inch 100-year Flood", - "description": "From Analyze Boston; a 9-inch flood event that has a 1 in 100 chance of occurring in any given year.", - "category": "flood", - "city": "Boston, MA", - "raw_data_type": "geojson", - "url": "https://data.kitware.com/api/v1/item/64f87bc16725af35134c1488/download", - "path": "boston/9in_100yr_flood.geojson", - "style": { - "options": { - "outline": "white", - "palette": [ - "blue" - ] - } - } - }, - { - "name": "21-inch Sea Level Rise", - "description": "From Analyze Boston; 21 inches of sea level rise is expected consistently across emissions scenarios by the end of the 2050s.", - "category": "flood", - "city": "Boston, MA", - "raw_data_type": "geojson", - "url": "https://data.kitware.com/api/v1/item/64f874486725af35134c147c/download", - "path": "boston/21in_rise.geojson", - "style": { - "options": { - "outline": "white", - "palette": [ - "blue" - ] - } - } - }, - { - "name": "21-inch 10-year Flood", - "description": "From Analyze Boston; a 21-inch flood event that has a 1 in 10 chance of occurring in any given year.", - "category": "flood", - "city": "Boston, MA", - "raw_data_type": "geojson", - "url": "https://data.kitware.com/api/v1/item/64f87bd56725af35134c148b/download", - "path": "boston/21in_10yr_flood.geojson", - "style": { - "options": { - "outline": "white", - "palette": [ - "blue" - ] - } - } - }, - { - "name": "21-inch 100-year Flood", - "description": "From Analyze Boston; a 21-inch flood event that has a 1 in 100 chance of occurring in any given year.", - "category": "flood", - "city": "Boston, MA", - "raw_data_type": "geojson", - "url": "https://data.kitware.com/api/v1/item/64f87be96725af35134c148e/download", - "path": "boston/21in_100yr_flood.geojson", - "style": { - "options": { - "outline": "white", - "palette": [ - "blue" - ] + "type": "vector", + "files": [ + { + "url": "https://data.kitware.com/api/v1/item/64fbb4c6e99e9e6006f00114/download", + "path": "boston/zipcodes.zip" } + ], + "region_options": { + "name_property": "GEOID20" + }, + "style_options": { + "outline": "white", + "palette": [ + "grey" + ] } }, { - "name": "36-inch Sea Level Rise", - "description": "From Analyze Boston; 36 inches of sea level rise has a 50% chance of occurring by the end of the century according to emissions scenarios.", + "name": "Boston Sea Level Rises", + "description": "From Analyze Boston: flood data for 9-inch, 21-inch, and 36-inch sea-level rise projections", "category": "flood", - "city": "Boston, MA", - "raw_data_type": "geojson", - "url": "https://data.kitware.com/api/v1/item/64f874566725af35134c147f/download", - "path": "boston/36in_rise.geojson", - "style": { - "options": { - "outline": "white", - "palette": [ - "blue" - ] + "type": "vector", + "files": [ + { + "url": "https://data.kitware.com/api/v1/item/64f8743d6725af35134c1479/download", + "path": "boston/9in_rise.geojson" + }, + { + "url": "https://data.kitware.com/api/v1/item/64f874486725af35134c147c/download", + "path": "boston/21in_rise.geojson" + }, + { + "url": "https://data.kitware.com/api/v1/item/64f874566725af35134c147f/download", + "path": "boston/36in_rise.geojson" } + ], + "style_options": { + "outline": "white", + "palette": [ + "blue" + ] } }, { - "name": "36-inch 10-year Flood", - "description": "From Analyze Boston; a 36-inch flood event that has a 1 in 10 chance of occurring in any given year.", + "name": "Boston 10-Year Flood Events", + "description": "From Analyze Boston: flood data for 9-inch, 21-inch, and 36-inch 10-year flood projections", "category": "flood", - "city": "Boston, MA", - "raw_data_type": "geojson", - "url": "https://data.kitware.com/api/v1/item/64f87bfd6725af35134c1491/download", - "path": "boston/36in_10yr_flood.geojson", - "style": { - "options": { - "outline": "white", - "palette": [ - "blue" - ] + "type": "vector", + "files": [ + { + "url": "https://data.kitware.com/api/v1/item/64f87ba86725af35134c1485/download", + "path": "boston/9in_10yr_flood.geojson" + }, + { + "url": "https://data.kitware.com/api/v1/item/64f87bd56725af35134c148b/download", + "path": "boston/21in_10yr_flood.geojson" + }, + { + "url": "https://data.kitware.com/api/v1/item/64f87bfd6725af35134c1491/download", + "path": "boston/36in_10yr_flood.geojson" } + ], + "style_options": { + "outline": "white", + "palette": [ + "blue" + ] } }, { - "name": "36-inch 100-year Flood", - "description": "From Analyze Boston; a 36-inch flood event that has a 1 in 100 chance of occurring in any given year.", + "name": "Boston 100-Year Flood Events", + "description": "From Analyze Boston: flood data for 9-inch, 21-inch, and 36-inch 100-year flood projections", "category": "flood", - "city": "Boston, MA", - "raw_data_type": "geojson", - "url": "https://data.kitware.com/api/v1/item/64f87c496725af35134c1494/download", - "path": "boston/36in_100yr_flood.geojson", - "style": { - "options": { - "outline": "white", - "palette": [ - "blue" - ] + "type": "vector", + "files": [ + { + "url": "https://data.kitware.com/api/v1/item/64f87bc16725af35134c1488/download", + "path": "boston/9in_100yr_flood.geojson" + }, + { + "url": "https://data.kitware.com/api/v1/item/64f87be96725af35134c148e/download", + "path": "boston/21in_100yr_flood.geojson" + }, + { + "url": "https://data.kitware.com/api/v1/item/64f87c496725af35134c1494/download", + "path": "boston/36in_100yr_flood.geojson" } + ], + "style_options": { + "outline": "white", + "palette": [ + "blue" + ] } }, { "name": "DC Metro", "description": "DC Metro Lines and Stations", "category": "transportation", - "city": "Washington, DC", - "raw_data_type": "shape_file_archive", - "url": "https://data.kitware.com/api/v1/item/64b80188cf6a0eaef6c1416e/download", - "path": "washington/DC_Metro.zip", - "network": true, - "metadata": { + "files": [ + { + "url": "https://data.kitware.com/api/v1/item/64b80188cf6a0eaef6c1416e/download", + "path": "washington/DC_Metro.zip" + } + ], + "network_options": { "connection_column": "LINE", "connection_column_delimiter": ", ", "node_id_column": "NAME" }, - "style": { - "property_map": { - "colors": "LINE" - }, - "options": { - "color_delimiter": ", ", - "outline": "white" - } + "style_options": { + "color_property": "LINE", + "color_delimiter": ", ", + "outline": "white" } } ] diff --git a/sample_data/ingest_sample_data.py b/sample_data/ingest_sample_data.py new file mode 100644 index 00000000..18c4108d --- /dev/null +++ b/sample_data/ingest_sample_data.py @@ -0,0 +1,156 @@ +from datetime import datetime +import json +import os +from pathlib import Path + +from django.contrib.gis.geos import Point +from django.core.files.base import ContentFile +import requests + +from uvdat.core.models import Chart, Context, Dataset, FileItem + + +def ingest_file(file_info, index=0, dataset=None, chart=None): + file_path = file_info.get('path') + file_name = file_info.get('name', file_path.split('/')[-1]) + file_url = file_info.get('url') + file_metadata = file_info.get('metadata', {}) + + file_location = Path('sample_data', file_path) + file_type = file_path.split('.')[-1] + if not file_location.exists(): + print(f'\t Downloading data file {file_name}.') + file_location.parent.mkdir(parents=True, exist_ok=True) + with open(file_location, 'wb') as f: + r = requests.get(file_url) + f.write(r.content) + + existing = FileItem.objects.filter(name=file_name) + if existing.count(): + print('\t', f'FileItem {file_name} already exists.') + else: + new_file_item = FileItem.objects.create( + name=file_name, + dataset=dataset, + chart=chart, + file_type=file_type, + file_size=os.path.getsize(file_location), + metadata=dict( + **file_metadata, + uploaded=str(datetime.now()), + ), + index=index, + ) + print('\t', f'FileItem {new_file_item.name} created.') + with file_location.open('rb') as f: + new_file_item.file.save(file_path, ContentFile(f.read())) + + +def ingest_contexts(): + print('Creating Context objects...') + with open('sample_data/contexts.json') as contexts_json: + data = json.load(contexts_json) + for context in data: + print('\t- ', context['name']) + existing = Context.objects.filter(name=context['name']) + if existing.count(): + context_for_setting = existing.first() + else: + context_for_setting = Context.objects.create( + name=context['name'], + default_map_center=Point(*context['default_map_center']), + default_map_zoom=context['default_map_zoom'], + ) + print('\t', f'Context {context_for_setting.name} created.') + + context_for_setting.datasets.set(Dataset.objects.filter(name__in=context['datasets'])) + + +def ingest_charts(): + print('Creating Chart objects...') + with open('sample_data/charts.json') as charts_json: + data = json.load(charts_json) + for chart in data: + print('\t- ', chart['name']) + existing = Chart.objects.filter(name=chart['name']) + if existing.count(): + chart_for_conversion = existing.first() + else: + new_chart = Chart.objects.create( + name=chart['name'], + description=chart['description'], + context=Context.objects.get(name=chart['context']), + chart_options=chart.get('chart_options'), + metadata=chart.get('metadata'), + editable=chart.get('editable', False), + ) + print('\t', f'Chart {new_chart.name} created.') + for index, file_info in enumerate(chart.get('files', [])): + ingest_file( + file_info, + index=index, + chart=new_chart, + ) + chart_for_conversion = new_chart + + print('\t', f'Converting data for {chart_for_conversion.name}...') + chart_for_conversion.spawn_conversion_task( + conversion_options=chart.get('conversion_options'), + asynchronous=False, + ) + + +def ingest_datasets(include_large=False, dataset_indexes=None): + print('Creating Dataset objects...') + with open('sample_data/datasets.json') as datasets_json: + data = json.load(datasets_json) + for index, dataset in enumerate(data): + # Grab fields specific to dataset classification + network_options = dataset.get('network_options') + region_options = dataset.get('region_options') + + if dataset_indexes is None or index in dataset_indexes: + print('\t- ', dataset['name']) + existing = Dataset.objects.filter(name=dataset['name']) + if existing.count(): + dataset_for_conversion = existing.first() + else: + # Determine classification + classification = Dataset.Classification.OTHER + if network_options: + classification = Dataset.Classification.NETWORK + elif region_options: + classification = Dataset.Classification.REGION + + # Create dataset + new_dataset = Dataset.objects.create( + name=dataset['name'], + description=dataset['description'], + category=dataset['category'], + dataset_type=dataset.get('type', 'vector').upper(), + metadata=dataset.get('metadata', {}), + classification=classification, + ) + print('\t', f'Dataset {new_dataset.name} created.') + for index, file_info in enumerate(dataset.get('files', [])): + ingest_file( + file_info, + index=index, + dataset=new_dataset, + ) + dataset_for_conversion = new_dataset + + dataset_size_mb = dataset_for_conversion.get_size() >> 20 + if include_large or dataset_size_mb < 50: + print('\t', f'Converting data for {dataset_for_conversion.name}...') + dataset_for_conversion.spawn_conversion_task( + style_options=dataset.get('style_options'), + network_options=network_options, + region_options=region_options, + asynchronous=False, + ) + else: + print( + '\t', f'Dataset too large ({dataset_size_mb} MB); skipping conversion step.' + ) + print('\t', 'Use `--include_large` to include conversions for large datasets.') diff --git a/sample_data/ingest_sample_data_output.txt b/sample_data/ingest_sample_data_output.txt new file mode 100644 index 00000000..e510a6d5 --- /dev/null +++ b/sample_data/ingest_sample_data_output.txt @@ -0,0 +1,112 @@ +root@70f88cf67dbf:/opt/django-project# python manage.py populate --include_large +------------------------------------ +Populating server with sample data... +Creating Context objects... + - Boston Transportation + Context Boston Transportation created. + - DC Transportation + Context DC Transportation created. + - Boston-Washington Transportation + Context Boston-Washington Transportation created. +Creating Chart objects... + - Boston Harbor Daily Tide Levels + Chart Boston Harbor Daily Tide Levels created. + FileItem tide_level_data.csv created. + Converting data for Boston Harbor Daily Tide Levels... + Saved converted data for chart Boston Harbor Daily Tide Levels. +Creating Dataset objects... + - MBTA Rapid Transit + Dataset MBTA Rapid Transit created. + FileItem mbta_rapid_transit.zip created. + Converting data for MBTA Rapid Transit... + VectorMapLayer 1 created. + 31 vector tiles created. + 158 nodes and 164 edges created. + - MBTA Commuter Rail + Dataset MBTA Commuter Rail created. + FileItem commuter_rail.zip created. + Converting data for MBTA Commuter Rail... + VectorMapLayer 2 created. + 686 vector tiles created. + 268 nodes and 269 edges created. + - Boston Hurricane Surge Inundation Zones + Dataset Boston Hurricane Surge Inundation Zones created. + FileItem hurr_inun.zip created. + Converting data for Boston Hurricane Surge Inundation Zones... + VectorMapLayer 3 created. + 295 vector tiles created. + - Boston FEMA National Flood Hazard + Dataset Boston FEMA National Flood Hazard created. + FileItem flood_hazard_fema.zip created. + Converting data for Boston FEMA National Flood Hazard... + VectorMapLayer 4 created. + 587 vector tiles created. + - Massachusetts Elevation Data + Dataset Massachusetts Elevation Data created. + FileItem easternmass.tif created. + Converting data for Massachusetts Elevation Data... + RasterMapLayer 1 created. + - Boston Neighborhoods + Dataset Boston Neighborhoods created. + FileItem neighborhoods2020.json created. + Converting data for Boston Neighborhoods... + VectorMapLayer 5 created. + 26 vector tiles created. + 24 regions created. + - Boston Census 2020 Block Groups + Dataset Boston Census 2020 Block Groups created. + FileItem blockgroups.zip created. + Converting data for Boston Census 2020 Block Groups... + VectorMapLayer 6 created. + 26 vector tiles created. + 581 regions created. + - Boston Zip Codes + Dataset Boston Zip Codes created. + FileItem zipcodes.zip created. + Converting data for Boston Zip Codes... + VectorMapLayer 7 created. + 780 vector tiles created. + 539 regions created. + - Boston Sea Level Rises + Dataset Boston Sea Level Rises created. + FileItem 9in_rise.geojson created. + FileItem 21in_rise.geojson created. + FileItem 36in_rise.geojson created. + Converting data for Boston Sea Level Rises... + VectorMapLayer 8 created. + 26 vector tiles created. + VectorMapLayer 9 created. + 26 vector tiles created. + VectorMapLayer 10 created. + 26 vector tiles created. + - Boston 10-Year Flood Events + Dataset Boston 10-Year Flood Events created. + FileItem 9in_10yr_flood.geojson created. + FileItem 21in_10yr_flood.geojson created. + FileItem 36in_10yr_flood.geojson created. + Converting data for Boston 10-Year Flood Events... + VectorMapLayer 11 created. + 26 vector tiles created. + VectorMapLayer 12 created. + 26 vector tiles created. + VectorMapLayer 13 created. + 26 vector tiles created. + - Boston 100-Year Flood Events + Dataset Boston 100-Year Flood Events created. + FileItem 9in_100yr_flood.geojson created. + FileItem 21in_100yr_flood.geojson created. + FileItem 36in_100yr_flood.geojson created. + Converting data for Boston 100-Year Flood Events... + VectorMapLayer 14 created. + 26 vector tiles created. + VectorMapLayer 15 created. + 26 vector tiles created. + VectorMapLayer 16 created. + 26 vector tiles created. + - DC Metro + Dataset DC Metro created. + FileItem DC_Metro.zip created. + Converting data for DC Metro... + VectorMapLayer 17 created. + 56 vector tiles created. + 98 nodes and 134 edges created. diff --git a/sample_data/populate_server.py b/sample_data/populate_server.py deleted file mode 100644 index 24c9b732..00000000 --- a/sample_data/populate_server.py +++ /dev/null @@ -1,101 +0,0 @@ -import json -from pathlib import Path - -from django.contrib.gis.geos import Point -from django.core.files.base import ContentFile -from django.core.management.base import BaseCommand -import requests - -from uvdat.core.models import Chart, City, Dataset -from uvdat.core.tasks.charts import convert_chart_data -from uvdat.core.tasks.conversion import convert_raw_data - - -class Command(BaseCommand): - requires_migrations_checks = True - - def handle(self, *args, **kwargs): - print('Creating new City objects...') - with open('sample_data/cities.json') as cities_json: - data = json.load(cities_json) - for city in data: - print('\t', city['name']) - existing = City.objects.filter(name=city['name']) - if existing.count(): - print('\t', 'already exists, deleting old copy.') - existing.delete() - City.objects.create( - name=city['name'], - center=Point( - x=city['latitude'], - y=city['longitude'], - ), - default_zoom=city['default_zoom'], - ) - - print('Creating new Dataset objects...') - with open('sample_data/datasets.json') as datasets_json: - data = json.load(datasets_json) - for dataset in data: - print('\t', dataset['name']) - existing = Dataset.objects.filter(name=dataset['name']) - if existing.count(): - print('\t', 'already exists, deleting old copy.') - existing.delete() - - new_dataset = Dataset.objects.create( - name=dataset['name'], - description=dataset['description'], - category=dataset['category'], - city=City.objects.get(name=dataset['city']), - raw_data_type=dataset['raw_data_type'], - metadata=dataset.get('metadata'), - style=dataset.get('style'), - network=dataset.get('network', False), - ) - archive_location = Path('sample_data', dataset['path']) - if not archive_location.exists(): - print('\t Downloading data file.') - archive_location.parent.mkdir(parents=True, exist_ok=True) - with open(archive_location, 'wb') as archive: - r = requests.get(dataset['url']) - archive.write(r.content) - with open(archive_location, 'rb') as archive: - new_dataset.raw_data_archive.save(archive_location, ContentFile(archive.read())) - print('\t Starting conversion task.') - convert_raw_data(new_dataset.id) - print() - - print('Creating new Chart objects...') - with open('sample_data/charts.json') as charts_json: - data = json.load(charts_json) - for chart in data: - print('\t', chart['name']) - existing = Chart.objects.filter(name=chart['name']) - if existing.count(): - print('\t', 'already exists, deleting old copy.') - existing.delete() - - new_chart = Chart.objects.create( - name=chart['name'], - description=chart['description'], - category=chart['category'], - city=City.objects.get(name=chart['city']), - raw_data_type=chart['raw_data_type'], - chart_options=chart.get('chart_options'), - metadata=chart.get('metadata'), - style=chart.get('style'), - clearable=chart.get('clearable', False), - ) - file_location = Path('sample_data', chart['path']) - if not file_location.exists(): - print('\t Downloading data file.') - file_location.parent.mkdir(parents=True, exist_ok=True) - with open(file_location, 'wb') as f: - r = requests.get(chart['url']) - f.write(r.content) - with open(file_location, 'rb') as f: - new_chart.raw_data_file.save(file_location, ContentFile(f.read())) - print('\t Starting conversion task.') - convert_chart_data(new_chart) - print() diff --git a/setup.py b/setup.py index b35a847a..b3aef18a 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,9 @@ # TODO: remove django version constraint # when girder4 becomes compatible with 4.2 'django-allauth<0.56.0', - 'django-configurations[database,email]', + # TODO: remove repo link after django-configurations 2.4.2 is released to PyPI + # https://github.com/jazzband/django-configurations/pull/365 + 'django-configurations[database,email] @ git+https://github.com/jazzband/django-configurations@master', 'django-extensions', 'django-filter', 'django-oauth-toolkit', diff --git a/tox.ini b/tox.ini index 94797cea..7bdf7b68 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = lint, - # test, + test, check-migrations, [testenv:lint] @@ -16,7 +16,7 @@ deps = flake8-quotes pep8-naming commands = - flake8 {posargs:.} + flake8 {posargs:uvdat} [testenv:type] skipsdist = true @@ -26,7 +26,7 @@ deps = django-stubs djangorestframework-stubs commands = - mypy {posargs:.} + mypy {posargs:uvdat} [testenv:format] skipsdist = true @@ -35,8 +35,8 @@ deps = black isort commands = - isort {posargs:.} - black {posargs:.} + isort {posargs:uvdat} + black {posargs:uvdat} [testenv:test] passenv = @@ -54,6 +54,7 @@ deps = pytest-factoryboy pytest-mock commands = + pip install large-image-converter --find-links https://girder.github.io/large_image_wheels pytest {posargs} [testenv:check-migrations] diff --git a/uvdat/core/admin.py b/uvdat/core/admin.py index 6d138ccc..dec0fda8 100644 --- a/uvdat/core/admin.py +++ b/uvdat/core/admin.py @@ -1,38 +1,115 @@ from django.contrib import admin -from uvdat.core.models import Chart, City, Dataset, NetworkNode, Region, SimulationResult - - -class CityAdmin(admin.ModelAdmin): +from uvdat.core.models import ( + Chart, + Context, + Dataset, + DerivedRegion, + FileItem, + NetworkEdge, + NetworkNode, + RasterMapLayer, + SimulationResult, + SourceRegion, + VectorMapLayer, + VectorTile, +) + + +class ContextAdmin(admin.ModelAdmin): list_display = ['id', 'name'] class DatasetAdmin(admin.ModelAdmin): - list_display = ['id', 'name', 'category'] + list_display = ['id', 'name', 'dataset_type', 'category'] -class NetworkNodeAdmin(admin.ModelAdmin): - list_display = ['name', 'dataset', 'get_adjacent'] +class FileItemAdmin(admin.ModelAdmin): + list_display = ['id', 'name', 'get_relationship'] - def get_adjacent(self, obj): - return ', '.join(n.name for n in obj.adjacent_nodes.all()) + def get_relationship(self, obj): + if obj.dataset is not None: + return obj.dataset.name + if obj.chart is not None: + return obj.chart.name + return 'None' -class RegionAdmin(admin.ModelAdmin): - list_display = ['id', 'name'] +class ChartAdmin(admin.ModelAdmin): + list_display = ['id', 'name', 'editable'] -class ChartAdmin(admin.ModelAdmin): - list_display = ['id', 'name'] +class RasterMapLayerAdmin(admin.ModelAdmin): + list_display = ['id', 'get_dataset_name', 'index'] + + def get_dataset_name(self, obj): + return obj.file_item.dataset.name + + +class VectorMapLayerAdmin(admin.ModelAdmin): + list_display = ['id', 'get_dataset_name', 'index'] + + def get_dataset_name(self, obj): + return obj.file_item.dataset.name + + +class VectorTileAdmin(admin.ModelAdmin): + list_display = ['id', 'get_fileitem_name', 'get_map_layer_index', 'x', 'y', 'z'] + + def get_fileitem_name(self, obj): + return obj.map_layer.file_item.name + + def get_map_layer_index(self, obj): + return obj.map_layer.index + + +class SourceRegionAdmin(admin.ModelAdmin): + list_display = ['id', 'name', 'get_dataset_name'] + + def get_dataset_name(self, obj): + return obj.dataset.name + + +class DerivedRegionAdmin(admin.ModelAdmin): + list_display = ['id', 'name', 'get_context_name', 'operation', 'get_source_region_names'] + + def get_context_name(self, obj): + return obj.context.name + + def get_source_region_names(self, obj): + return ', '.join(r.name for r in obj.source_regions.all()) + + +class NetworkEdgeAdmin(admin.ModelAdmin): + list_display = ['id', 'name', 'get_dataset_name'] + + def get_dataset_name(self, obj): + return obj.dataset.name + + +class NetworkNodeAdmin(admin.ModelAdmin): + list_display = ['id', 'name', 'get_dataset_name', 'get_adjacent_node_names'] + + def get_dataset_name(self, obj): + return obj.dataset.name + + def get_adjacent_node_names(self, obj): + return ', '.join(n.name for n in obj.get_adjacent_nodes()) class SimulationResultAdmin(admin.ModelAdmin): - list_display = ['id', 'simulation_id', 'input_args'] + list_display = ['id', 'simulation_type', 'input_args'] -admin.site.register(City, CityAdmin) +admin.site.register(Context, ContextAdmin) admin.site.register(Dataset, DatasetAdmin) -admin.site.register(NetworkNode, NetworkNodeAdmin) -admin.site.register(Region, RegionAdmin) +admin.site.register(FileItem, FileItemAdmin) admin.site.register(Chart, ChartAdmin) +admin.site.register(RasterMapLayer, RasterMapLayerAdmin) +admin.site.register(VectorMapLayer, VectorMapLayerAdmin) +admin.site.register(VectorTile, VectorTileAdmin) +admin.site.register(SourceRegion, SourceRegionAdmin) +admin.site.register(DerivedRegion, DerivedRegionAdmin) +admin.site.register(NetworkNode, NetworkNodeAdmin) +admin.site.register(NetworkEdge, NetworkEdgeAdmin) admin.site.register(SimulationResult, SimulationResultAdmin) diff --git a/uvdat/core/management/commands/populate.py b/uvdat/core/management/commands/populate.py index 6b5bda2c..8c8abffa 100644 --- a/uvdat/core/management/commands/populate.py +++ b/uvdat/core/management/commands/populate.py @@ -1,3 +1,29 @@ -from sample_data.populate_server import Command +from django.core.management.base import BaseCommand -__all__ = [Command] +from sample_data.ingest_sample_data import ingest_charts, ingest_contexts, ingest_datasets + + +class Command(BaseCommand): + requires_migrations_checks = True + + def add_arguments(self, parser): + parser.add_argument( + '--include_large', + action='store_true', + help='Include conversion step for large datasets', + ) + parser.add_argument('--dataset_indexes', nargs='*', type=int) + + def handle(self, *args, **kwargs): + print('Populating server with sample data...') + include_large = kwargs['include_large'] + dataset_indexes = kwargs['dataset_indexes'] + if dataset_indexes is None or len(dataset_indexes) == 0: + dataset_indexes = None + + ingest_datasets( + include_large=include_large, + dataset_indexes=dataset_indexes, + ) + ingest_contexts() + ingest_charts() diff --git a/uvdat/core/migrations/0001_default_site.py b/uvdat/core/migrations/0001_default_site.py deleted file mode 100644 index 0b66ca1b..00000000 --- a/uvdat/core/migrations/0001_default_site.py +++ /dev/null @@ -1,40 +0,0 @@ -from django.conf import settings -from django.db import migrations -from django.db.backends.base.schema import BaseDatabaseSchemaEditor -from django.db.migrations.state import StateApps - - -def update_default_site(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor): - Site = apps.get_model('sites', 'Site') # noqa: N806 - - # A default site object may or may not exist. - # If this is a brand-new database, the post_migrate will not fire until the very end of the - # "migrate" command, so the sites app will not have created a default site object yet. - # If this is an existing database, the sites app will likely have created an default site - # object already. - Site.objects.update_or_create( - pk=settings.SITE_ID, - defaults={ - 'domain': 'www.uvdat.test', - 'name': 'UVDAT', - }, - ) - - -def rollback_default_site(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor): - Site = apps.get_model('sites', 'Site') # noqa: N806 - - # This is the initial value of the default site object, as populated by the sites app. - # If it doesn't exist for some reason, there is nothing to roll back. - Site.objects.filter(pk=settings.SITE_ID).update(domain='example.com', name='example.com') - - -class Migration(migrations.Migration): - dependencies = [ - # This is the final sites app migration - ('sites', '0002_alter_domain_unique'), - ] - - operations = [ - migrations.RunPython(update_default_site, rollback_default_site), - ] diff --git a/uvdat/core/migrations/0001_models_redesign.py b/uvdat/core/migrations/0001_models_redesign.py new file mode 100644 index 00000000..fe404ef9 --- /dev/null +++ b/uvdat/core/migrations/0001_models_redesign.py @@ -0,0 +1,426 @@ +# Generated by Django 4.1 on 2023-11-15 21:41 + +import django.contrib.gis.db.models.fields +from django.db import migrations, models +import django.db.models.deletion +import django_extensions.db.fields +import s3_file_field.fields + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name='Chart', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ('name', models.CharField(max_length=255, unique=True)), + ('description', models.TextField(blank=True, null=True)), + ('metadata', models.JSONField(blank=True, null=True)), + ('chart_data', models.JSONField(blank=True, null=True)), + ('chart_options', models.JSONField(blank=True, null=True)), + ('editable', models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name='Context', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ('name', models.CharField(max_length=255, unique=True)), + ('default_map_center', django.contrib.gis.db.models.fields.PointField(srid=4326)), + ('default_map_zoom', models.IntegerField(default=10)), + ], + ), + migrations.CreateModel( + name='Dataset', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ('name', models.CharField(max_length=255, unique=True)), + ('description', models.TextField(blank=True, null=True)), + ('category', models.CharField(max_length=25)), + ('processing', models.BooleanField(default=False)), + ('metadata', models.JSONField(blank=True, null=True)), + ( + 'dataset_type', + models.CharField( + choices=[('VECTOR', 'Vector'), ('RASTER', 'Raster')], max_length=6 + ), + ), + ( + 'classification', + models.CharField( + choices=[('Network', 'Network'), ('Region', 'Region'), ('Other', 'Other')], + default='Other', + max_length=16, + ), + ), + ], + ), + migrations.CreateModel( + name='FileItem', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ( + 'created', + django_extensions.db.fields.CreationDateTimeField( + auto_now_add=True, verbose_name='created' + ), + ), + ( + 'modified', + django_extensions.db.fields.ModificationDateTimeField( + auto_now=True, verbose_name='modified' + ), + ), + ('name', models.CharField(max_length=50)), + ('file', s3_file_field.fields.S3FileField()), + ('file_type', models.CharField(max_length=25)), + ('file_size', models.IntegerField(null=True)), + ('metadata', models.JSONField(blank=True, null=True)), + ('index', models.IntegerField(null=True)), + ( + 'chart', + models.ForeignKey( + null=True, on_delete=django.db.models.deletion.CASCADE, to='core.chart' + ), + ), + ( + 'dataset', + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='source_files', + to='core.dataset', + ), + ), + ], + options={ + 'get_latest_by': 'modified', + 'abstract': False, + }, + ), + migrations.CreateModel( + name='VectorMapLayer', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ( + 'created', + django_extensions.db.fields.CreationDateTimeField( + auto_now_add=True, verbose_name='created' + ), + ), + ( + 'modified', + django_extensions.db.fields.ModificationDateTimeField( + auto_now=True, verbose_name='modified' + ), + ), + ('metadata', models.JSONField(blank=True, null=True)), + ('default_style', models.JSONField(blank=True, null=True)), + ('index', models.IntegerField(null=True)), + ('geojson_file', s3_file_field.fields.S3FileField(null=True)), + ( + 'file_item', + models.ForeignKey( + null=True, on_delete=django.db.models.deletion.CASCADE, to='core.fileitem' + ), + ), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='VectorTile', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ('geojson_data', models.JSONField(blank=True, null=True)), + ('x', models.IntegerField(default=0)), + ('y', models.IntegerField(default=0)), + ('z', models.IntegerField(default=0)), + ( + 'map_layer', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to='core.vectormaplayer' + ), + ), + ], + ), + migrations.CreateModel( + name='SourceRegion', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ('name', models.CharField(max_length=255)), + ('metadata', models.JSONField(blank=True, null=True)), + ('boundary', django.contrib.gis.db.models.fields.MultiPolygonField(srid=4326)), + ( + 'dataset', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='regions', + to='core.dataset', + ), + ), + ], + ), + migrations.CreateModel( + name='SimulationResult', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ( + 'created', + django_extensions.db.fields.CreationDateTimeField( + auto_now_add=True, verbose_name='created' + ), + ), + ( + 'modified', + django_extensions.db.fields.ModificationDateTimeField( + auto_now=True, verbose_name='modified' + ), + ), + ( + 'simulation_type', + models.CharField( + choices=[ + ('FLOOD_1', 'Flood Scenario 1'), + ('RECOVERY', 'Recovery Scenario'), + ], + max_length=8, + ), + ), + ('input_args', models.JSONField(blank=True, null=True)), + ('output_data', models.JSONField(blank=True, null=True)), + ('error_message', models.TextField(blank=True, null=True)), + ( + 'context', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='simulation_results', + to='core.context', + ), + ), + ], + options={ + 'get_latest_by': 'modified', + 'abstract': False, + }, + ), + migrations.CreateModel( + name='RasterMapLayer', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ( + 'created', + django_extensions.db.fields.CreationDateTimeField( + auto_now_add=True, verbose_name='created' + ), + ), + ( + 'modified', + django_extensions.db.fields.ModificationDateTimeField( + auto_now=True, verbose_name='modified' + ), + ), + ('metadata', models.JSONField(blank=True, null=True)), + ('default_style', models.JSONField(blank=True, null=True)), + ('index', models.IntegerField(null=True)), + ('cloud_optimized_geotiff', s3_file_field.fields.S3FileField()), + ( + 'file_item', + models.ForeignKey( + null=True, on_delete=django.db.models.deletion.CASCADE, to='core.fileitem' + ), + ), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='NetworkNode', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ('name', models.CharField(max_length=255, unique=True)), + ('metadata', models.JSONField(blank=True, null=True)), + ('capacity', models.IntegerField(null=True)), + ('location', django.contrib.gis.db.models.fields.PointField(srid=4326)), + ( + 'dataset', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='network_nodes', + to='core.dataset', + ), + ), + ], + ), + migrations.CreateModel( + name='NetworkEdge', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ('name', models.CharField(max_length=255, unique=True)), + ('metadata', models.JSONField(blank=True, null=True)), + ('capacity', models.IntegerField(null=True)), + ('line_geometry', django.contrib.gis.db.models.fields.LineStringField(srid=4326)), + ('directed', models.BooleanField(default=False)), + ( + 'dataset', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='network_edges', + to='core.dataset', + ), + ), + ( + 'from_node', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='+', + to='core.networknode', + ), + ), + ( + 'to_node', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='+', + to='core.networknode', + ), + ), + ], + ), + migrations.CreateModel( + name='DerivedRegion', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ('name', models.CharField(max_length=255)), + ('metadata', models.JSONField(blank=True, null=True)), + ('boundary', django.contrib.gis.db.models.fields.MultiPolygonField(srid=4326)), + ( + 'operation', + models.CharField( + choices=[('UNION', 'Union'), ('INTERSECTION', 'Intersection')], + max_length=12, + ), + ), + ( + 'context', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='derived_regions', + to='core.context', + ), + ), + ( + 'map_layer', + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to='core.vectormaplayer' + ), + ), + ( + 'source_regions', + models.ManyToManyField(related_name='derived_regions', to='core.sourceregion'), + ), + ], + ), + migrations.AddField( + model_name='context', + name='datasets', + field=models.ManyToManyField(blank=True, to='core.dataset'), + ), + migrations.AddField( + model_name='chart', + name='context', + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='charts', + to='core.context', + ), + ), + migrations.AddIndex( + model_name='vectortile', + index=models.Index(fields=['z', 'x', 'y'], name='vectortile-coordinates-index'), + ), + migrations.AddConstraint( + model_name='vectortile', + constraint=models.UniqueConstraint( + fields=('map_layer', 'z', 'x', 'y'), name='unique-map-layer-index' + ), + ), + migrations.AddConstraint( + model_name='sourceregion', + constraint=models.UniqueConstraint( + fields=('dataset', 'name'), name='unique-source-region-name' + ), + ), + migrations.AddConstraint( + model_name='derivedregion', + constraint=models.UniqueConstraint( + fields=('context', 'name'), name='unique-derived-region-name' + ), + ), + ] diff --git a/uvdat/core/migrations/0002_city_and_dataset.py b/uvdat/core/migrations/0002_city_and_dataset.py deleted file mode 100644 index 440c3ab4..00000000 --- a/uvdat/core/migrations/0002_city_and_dataset.py +++ /dev/null @@ -1,95 +0,0 @@ -# Generated by Django 4.1 on 2023-05-17 16:08 - -import django.contrib.gis.db.models.fields -from django.contrib.postgres.operations import CreateExtension -from django.db import migrations, models -import django.db.models.deletion -import django_extensions.db.fields -import s3_file_field.fields - - -class Migration(migrations.Migration): - initial = True - - dependencies = [ - ('core', '0001_default_site'), - ] - - operations = [ - CreateExtension('postgis'), - migrations.CreateModel( - name='City', - fields=[ - ( - 'id', - models.BigAutoField( - auto_created=True, primary_key=True, serialize=False, verbose_name='ID' - ), - ), - ( - 'created', - django_extensions.db.fields.CreationDateTimeField( - auto_now_add=True, verbose_name='created' - ), - ), - ( - 'modified', - django_extensions.db.fields.ModificationDateTimeField( - auto_now=True, verbose_name='modified' - ), - ), - ('name', models.CharField(max_length=255, unique=True)), - ('center', django.contrib.gis.db.models.fields.PointField(srid=4326)), - ('default_zoom', models.IntegerField(default=10)), - ], - options={ - 'get_latest_by': 'modified', - 'abstract': False, - 'verbose_name_plural': 'cities', - }, - ), - migrations.CreateModel( - name='Dataset', - fields=[ - ( - 'id', - models.BigAutoField( - auto_created=True, primary_key=True, serialize=False, verbose_name='ID' - ), - ), - ( - 'created', - django_extensions.db.fields.CreationDateTimeField( - auto_now_add=True, verbose_name='created' - ), - ), - ( - 'modified', - django_extensions.db.fields.ModificationDateTimeField( - auto_now=True, verbose_name='modified' - ), - ), - ('name', models.CharField(max_length=255, unique=True)), - ('description', models.TextField(blank=True, null=True)), - ('category', models.CharField(max_length=25)), - ('style', models.JSONField(blank=True, null=True)), - ('raw_data_archive', s3_file_field.fields.S3FileField(blank=True, null=True)), - ('raw_data_type', models.CharField(default='shape_file_archive', max_length=25)), - ( - 'city', - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to='core.city', - related_name='datasets', - ), - ), - ('geodata_file', s3_file_field.fields.S3FileField(blank=True, null=True)), - ('vector_tiles_file', s3_file_field.fields.S3FileField(blank=True, null=True)), - ('raster_file', s3_file_field.fields.S3FileField(blank=True, null=True)), - ], - options={ - 'get_latest_by': 'modified', - 'abstract': False, - }, - ), - ] diff --git a/uvdat/core/migrations/0003_processing_flag.py b/uvdat/core/migrations/0003_processing_flag.py deleted file mode 100644 index 6d27050b..00000000 --- a/uvdat/core/migrations/0003_processing_flag.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.1 on 2023-07-07 14:48 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('core', '0002_city_and_dataset'), - ] - - operations = [ - migrations.AlterModelOptions( - name='city', - options={'verbose_name_plural': 'cities'}, - ), - migrations.AddField( - model_name='dataset', - name='processing', - field=models.BooleanField(default=False), - ), - ] diff --git a/uvdat/core/migrations/0004_network_nodes.py b/uvdat/core/migrations/0004_network_nodes.py deleted file mode 100644 index 9742a50e..00000000 --- a/uvdat/core/migrations/0004_network_nodes.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 4.1 on 2023-07-11 12:57 - -import django.contrib.gis.db.models.fields -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ('core', '0003_processing_flag'), - ] - - operations = [ - migrations.AddField( - model_name='dataset', - name='network', - field=models.BooleanField(default=False), - ), - migrations.CreateModel( - name='NetworkNode', - fields=[ - ( - 'id', - models.BigAutoField( - auto_created=True, primary_key=True, serialize=False, verbose_name='ID' - ), - ), - ('name', models.CharField(max_length=255, unique=True)), - ('location', django.contrib.gis.db.models.fields.PointField(srid=4326)), - ('properties', models.JSONField(blank=True, null=True)), - ('adjacent_nodes', models.ManyToManyField(to='core.networknode')), - ( - 'dataset', - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name='network_nodes', - to='core.dataset', - ), - ), - ], - ), - ] diff --git a/uvdat/core/migrations/0005_dataset_metadata.py b/uvdat/core/migrations/0005_dataset_metadata.py deleted file mode 100644 index 00e35175..00000000 --- a/uvdat/core/migrations/0005_dataset_metadata.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.1 on 2023-07-24 17:24 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('core', '0004_network_nodes'), - ] - - operations = [ - migrations.AddField( - model_name='dataset', - name='metadata', - field=models.JSONField(blank=True, null=True), - ), - ] diff --git a/uvdat/core/migrations/0006_regions.py b/uvdat/core/migrations/0006_regions.py deleted file mode 100644 index c91c9d2d..00000000 --- a/uvdat/core/migrations/0006_regions.py +++ /dev/null @@ -1,44 +0,0 @@ -# Generated by Django 4.1 on 2023-08-10 21:57 - -import django.contrib.gis.db.models.fields -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ('core', '0005_dataset_metadata'), - ] - - operations = [ - migrations.CreateModel( - name='Region', - fields=[ - ( - 'id', - models.BigAutoField( - auto_created=True, primary_key=True, serialize=False, verbose_name='ID' - ), - ), - ('name', models.CharField(max_length=255, unique=True)), - ('properties', models.JSONField(blank=True, null=True)), - ('boundary', django.contrib.gis.db.models.fields.MultiPolygonField(srid=4326)), - ( - 'city', - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name='regions', - to='core.city', - ), - ), - ( - 'dataset', - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name='regions', - to='core.dataset', - ), - ), - ], - ), - ] diff --git a/uvdat/core/migrations/0007_charts.py b/uvdat/core/migrations/0007_charts.py deleted file mode 100644 index 721a4bff..00000000 --- a/uvdat/core/migrations/0007_charts.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 4.1 on 2023-08-29 17:47 - -from django.db import migrations, models -import django.db.models.deletion -import s3_file_field.fields - - -class Migration(migrations.Migration): - dependencies = [ - ('core', '0006_regions'), - ] - - operations = [ - migrations.CreateModel( - name='Chart', - fields=[ - ( - 'id', - models.BigAutoField( - auto_created=True, primary_key=True, serialize=False, verbose_name='ID' - ), - ), - ('name', models.CharField(max_length=255, unique=True)), - ('description', models.TextField(blank=True, null=True)), - ('category', models.CharField(max_length=25)), - ('raw_data_file', s3_file_field.fields.S3FileField(blank=True, null=True)), - ('raw_data_type', models.CharField(default='csv', max_length=25)), - ('chart_data', models.JSONField(blank=True, null=True)), - ('chart_options', models.JSONField(blank=True, null=True)), - ('metadata', models.JSONField(blank=True, null=True)), - ('style', models.JSONField(blank=True, null=True)), - ('clearable', models.BooleanField(default=False)), - ( - 'city', - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name='charts', - to='core.city', - ), - ), - ], - ), - ] diff --git a/uvdat/core/migrations/0008_simulation_results.py b/uvdat/core/migrations/0008_simulation_results.py deleted file mode 100644 index 45b33683..00000000 --- a/uvdat/core/migrations/0008_simulation_results.py +++ /dev/null @@ -1,55 +0,0 @@ -# Generated by Django 4.1 on 2023-09-21 17:18 - -from django.db import migrations, models -import django.db.models.deletion -import django_extensions.db.fields - - -class Migration(migrations.Migration): - dependencies = [ - ('core', '0007_charts'), - ] - - operations = [ - migrations.CreateModel( - name='SimulationResult', - fields=[ - ( - 'id', - models.BigAutoField( - auto_created=True, primary_key=True, serialize=False, verbose_name='ID' - ), - ), - ( - 'created', - django_extensions.db.fields.CreationDateTimeField( - auto_now_add=True, verbose_name='created' - ), - ), - ( - 'modified', - django_extensions.db.fields.ModificationDateTimeField( - auto_now=True, verbose_name='modified' - ), - ), - ('simulation_id', models.IntegerField()), - ('input_args', models.JSONField(blank=True, null=True)), - ('output_data', models.JSONField(blank=True, null=True)), - ('error_message', models.TextField(blank=True, null=True)), - ( - 'city', - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name='simulation_results', - to='core.city', - ), - ), - ], - ), - migrations.AddConstraint( - model_name='simulationresult', - constraint=models.UniqueConstraint( - fields=('simulation_id', 'city', 'input_args'), name='unique_simulation_combination' - ), - ), - ] diff --git a/uvdat/core/migrations/0009_derived_regions_and_region_constraints.py b/uvdat/core/migrations/0009_derived_regions_and_region_constraints.py deleted file mode 100644 index fe813403..00000000 --- a/uvdat/core/migrations/0009_derived_regions_and_region_constraints.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 4.1 on 2023-09-27 19:45 - -import django.contrib.gis.db.models.fields -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0008_simulation_results'), - ] - - operations = [ - migrations.CreateModel( - name='DerivedRegion', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('properties', models.JSONField(blank=True, null=True)), - ('boundary', django.contrib.gis.db.models.fields.MultiPolygonField(srid=4326)), - ('source_operation', models.CharField(choices=[('UNION', 'Union'), ('INTERSECTION', 'Intersection')], max_length=12)), - ], - ), - migrations.AlterField( - model_name='region', - name='name', - field=models.CharField(max_length=255), - ), - migrations.AddConstraint( - model_name='region', - constraint=models.UniqueConstraint(fields=('dataset', 'name'), name='unique-name-per-dataset'), - ), - migrations.AddField( - model_name='derivedregion', - name='city', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='derived_regions', to='core.city'), - ), - migrations.AddField( - model_name='derivedregion', - name='source_regions', - field=models.ManyToManyField(related_name='derived_regions', to='core.region'), - ), - migrations.AddConstraint( - model_name='derivedregion', - constraint=models.UniqueConstraint(fields=('city', 'name'), name='unique-name-per-city'), - ), - ] diff --git a/uvdat/core/models.py b/uvdat/core/models.py deleted file mode 100644 index 86ea109b..00000000 --- a/uvdat/core/models.py +++ /dev/null @@ -1,114 +0,0 @@ -from django.contrib.gis.db import models as geo_models -from django.db import models -from django_extensions.db.models import TimeStampedModel -from s3_file_field import S3FileField - - -class City(TimeStampedModel): - name = models.CharField(max_length=255, unique=True) - center = geo_models.PointField() - default_zoom = models.IntegerField(default=10) - - class Meta: - verbose_name_plural = 'cities' - - -class Dataset(TimeStampedModel): - name = models.CharField(max_length=255, unique=True) - description = models.TextField(null=True, blank=True) - city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='datasets') - category = models.CharField(max_length=25) - style = models.JSONField(blank=True, null=True) - network = models.BooleanField(default=False) - processing = models.BooleanField(default=False) - metadata = models.JSONField(blank=True, null=True) - - # A ZIP file containing the original data files - raw_data_archive = S3FileField(null=True, blank=True) - raw_data_type = models.CharField(max_length=25, default='shape_file_archive') - - # GeoJSON file, containing geometries and other properties for features - geodata_file = S3FileField(null=True, blank=True) - - # JSON file, containing GeoJSON tiles, nested by z-x-y tile coordinates - vector_tiles_file = S3FileField(null=True, blank=True) - - # Raster file, containing a cloud-optimized geotiff - raster_file = S3FileField(null=True, blank=True) - - -class NetworkNode(models.Model): - name = models.CharField(max_length=255, unique=True) - location = geo_models.PointField() - properties = models.JSONField(blank=True, null=True) - dataset = models.ForeignKey(Dataset, on_delete=models.CASCADE, related_name='network_nodes') - adjacent_nodes = models.ManyToManyField('NetworkNode') - - -class Region(models.Model): - name = models.CharField(max_length=255) - properties = models.JSONField(blank=True, null=True) - boundary = geo_models.MultiPolygonField() - city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='regions') - dataset = models.ForeignKey(Dataset, on_delete=models.CASCADE, related_name='regions') - - class Meta: - constraints = [ - models.UniqueConstraint(name='unique-name-per-dataset', fields=['dataset', 'name']) - ] - - -class DerivedRegion(models.Model): - """A Region that's derived from other regions.""" - - class VectorOperation(models.TextChoices): - UNION = 'UNION', 'Union' - INTERSECTION = 'INTERSECTION', 'Intersection' - - name = models.CharField(max_length=255) - city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='derived_regions') - properties = models.JSONField(blank=True, null=True) - boundary = geo_models.MultiPolygonField() - - # Data from the source regions - source_regions = models.ManyToManyField(Region, related_name='derived_regions') - source_operation = models.CharField( - max_length=max(len(choice[0]) for choice in VectorOperation.choices), - choices=VectorOperation.choices, - ) - - class Meta: - constraints = [ - # We enforce name uniqueness across cities, since - # DerivedRegions can consist of regions from multiple datasets - models.UniqueConstraint(name='unique-name-per-city', fields=['city', 'name']) - ] - - -class Chart(models.Model): - name = models.CharField(max_length=255, unique=True) - description = models.TextField(null=True, blank=True) - city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='charts') - category = models.CharField(max_length=25) - raw_data_file = S3FileField(null=True, blank=True) - raw_data_type = models.CharField(max_length=25, default='csv') - chart_data = models.JSONField(blank=True, null=True) - chart_options = models.JSONField(blank=True, null=True) - metadata = models.JSONField(blank=True, null=True) - style = models.JSONField(blank=True, null=True) - clearable = models.BooleanField(default=False) - - -class SimulationResult(TimeStampedModel): - simulation_id = models.IntegerField() - city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='simulation_results') - input_args = models.JSONField(blank=True, null=True) - output_data = models.JSONField(blank=True, null=True) - error_message = models.TextField(null=True, blank=True) - - class Meta: - constraints = [ - models.UniqueConstraint( - fields=['simulation_id', 'city', 'input_args'], name='unique_simulation_combination' - ) - ] diff --git a/uvdat/core/models/__init__.py b/uvdat/core/models/__init__.py new file mode 100644 index 00000000..908b9769 --- /dev/null +++ b/uvdat/core/models/__init__.py @@ -0,0 +1,23 @@ +from .chart import Chart +from .context import Context +from .dataset import Dataset +from .file_item import FileItem +from .map_layers import RasterMapLayer, VectorMapLayer, VectorTile +from .networks import NetworkEdge, NetworkNode +from .regions import DerivedRegion, SourceRegion +from .simulations import SimulationResult + +__all__ = [ + Chart, + Context, + Dataset, + FileItem, + RasterMapLayer, + VectorMapLayer, + VectorTile, + SourceRegion, + DerivedRegion, + NetworkEdge, + NetworkNode, + SimulationResult, +] diff --git a/uvdat/core/models/chart.py b/uvdat/core/models/chart.py new file mode 100644 index 00000000..48540428 --- /dev/null +++ b/uvdat/core/models/chart.py @@ -0,0 +1,41 @@ +from django.db import models + +from .context import Context + + +class Chart(models.Model): + name = models.CharField(max_length=255, unique=True) + description = models.TextField(null=True, blank=True) + context = models.ForeignKey(Context, on_delete=models.CASCADE, related_name='charts') + metadata = models.JSONField(blank=True, null=True) + + chart_data = models.JSONField(blank=True, null=True) + chart_options = models.JSONField(blank=True, null=True) + editable = models.BooleanField(default=False) + + def is_in_context(self, context_id): + return self.context.id == context_id + + def spawn_conversion_task( + self, + conversion_options=None, + asynchronous=True, + ): + from uvdat.core.tasks.chart import convert_chart + + if asynchronous: + convert_chart.delay(self.id, conversion_options) + else: + convert_chart(self.id, conversion_options) + + def new_line(self): + # TODO: new line + pass + + def rename_lines(self, new_names): + # TODO: rename lines + pass + + def clear(self): + # TODO: clear + pass diff --git a/uvdat/core/models/context.py b/uvdat/core/models/context.py new file mode 100644 index 00000000..46e0e1ee --- /dev/null +++ b/uvdat/core/models/context.py @@ -0,0 +1,11 @@ +from django.contrib.gis.db import models as geo_models +from django.db import models + +from .dataset import Dataset + + +class Context(models.Model): + name = models.CharField(max_length=255, unique=True) + default_map_center = geo_models.PointField() + default_map_zoom = models.IntegerField(default=10) + datasets = models.ManyToManyField(Dataset, blank=True) diff --git a/uvdat/core/models/dataset.py b/uvdat/core/models/dataset.py new file mode 100644 index 00000000..8374ceaf --- /dev/null +++ b/uvdat/core/models/dataset.py @@ -0,0 +1,131 @@ +from django.db import models + + +class Dataset(models.Model): + class DatasetType(models.TextChoices): + VECTOR = 'VECTOR', 'Vector' + RASTER = 'RASTER', 'Raster' + + class Classification(models.TextChoices): + NETWORK = 'Network' + REGION = 'Region' + OTHER = 'Other' + + name = models.CharField(max_length=255, unique=True) + description = models.TextField(null=True, blank=True) + category = models.CharField(max_length=25) + processing = models.BooleanField(default=False) + metadata = models.JSONField(blank=True, null=True) + dataset_type = models.CharField( + max_length=max(len(choice[0]) for choice in DatasetType.choices), + choices=DatasetType.choices, + ) + classification = models.CharField( + max_length=16, choices=Classification.choices, default=Classification.OTHER + ) + + def is_in_context(self, context_id): + from uvdat.core.models import Context + + context = Context.objects.get(id=context_id) + return context.datasets.filter(id=self.id).exists() + + def spawn_conversion_task( + self, + style_options=None, + network_options=None, + region_options=None, + asynchronous=True, + ): + from uvdat.core.tasks.dataset import convert_dataset + + if asynchronous: + convert_dataset.delay(self.id, style_options, network_options, region_options) + else: + convert_dataset(self.id, style_options, network_options, region_options) + + def get_size(self): + from uvdat.core.models import FileItem + + size = 0 + for file_item in FileItem.objects.filter(dataset=self): + if file_item.file_size is not None: + size += file_item.file_size + return size + + def get_regions(self): + from uvdat.core.models import SourceRegion + + return SourceRegion.objects.filter(dataset=self) + + def get_network(self): + from uvdat.core.models import NetworkEdge, NetworkNode + + network = { + 'nodes': NetworkNode.objects.filter(dataset=self), + 'edges': NetworkEdge.objects.filter(dataset=self), + } + if len(network.get('nodes')) == 0 and len(network.get('edges')) == 0: + return None + return network + + def get_network_graph(self): + from uvdat.core.tasks.networks import get_dataset_network_graph + + return get_dataset_network_graph(self) + + def get_network_gcc(self, exclude_nodes): + from uvdat.core.tasks.networks import get_dataset_network_gcc + + return get_dataset_network_gcc(self, exclude_nodes) + + def get_map_layers(self): + """Return a queryset of either RasterMapLayer, or VectorMapLayer.""" + from uvdat.core.models import RasterMapLayer, VectorMapLayer + + if self.dataset_type == self.DatasetType.RASTER: + return RasterMapLayer.objects.filter(file_item__dataset=self) + if self.dataset_type == self.DatasetType.VECTOR: + return VectorMapLayer.objects.filter(file_item__dataset=self) + + raise NotImplementedError(f'Dataset Type {self.dataset_type}') + + def get_map_layer_tile_extents(self): + """ + Return the extents of all vector map layers of this dataset. + + Returns `None` if the dataset is not a vector dataset. + """ + if self.dataset_type != self.DatasetType.VECTOR: + return None + + from uvdat.core.models import VectorMapLayer, VectorTile + + # Retrieve all layers + layer_ids = VectorMapLayer.objects.filter(file_item__dataset=self).values_list( + 'id', flat=True + ) + + # Return x/y extents by layer id and z depth + vals = ( + VectorTile.objects.filter(map_layer_id__in=layer_ids) + .values('map_layer_id', 'z') + .annotate( + min_x=models.Min('x'), + min_y=models.Min('y'), + max_x=models.Max('x'), + max_y=models.Max('y'), + ) + .order_by('map_layer_id') + ) + + # Deconstruct query into response format + layers = {} + for entry in vals: + map_layer_id = entry.pop('map_layer_id') + if map_layer_id not in layers: + layers[map_layer_id] = {} + + layers[map_layer_id][entry.pop('z')] = entry + + return layers diff --git a/uvdat/core/models/file_item.py b/uvdat/core/models/file_item.py new file mode 100644 index 00000000..9a129a94 --- /dev/null +++ b/uvdat/core/models/file_item.py @@ -0,0 +1,26 @@ +from django.db import models +from django_extensions.db.models import TimeStampedModel +from s3_file_field import S3FileField + +from .chart import Chart +from .dataset import Dataset + + +class FileItem(TimeStampedModel): + name = models.CharField(max_length=50) + dataset = models.ForeignKey( + Dataset, on_delete=models.CASCADE, related_name='source_files', null=True + ) + chart = models.ForeignKey(Chart, on_delete=models.CASCADE, null=True) + file = S3FileField() + file_type = models.CharField(max_length=25) + file_size = models.IntegerField(null=True) + metadata = models.JSONField(blank=True, null=True) + index = models.IntegerField(null=True) + + def is_in_context(self, context_id): + return self.dataset.is_in_context(context_id) + + def download(self): + # TODO: download + pass diff --git a/uvdat/core/models/map_layers.py b/uvdat/core/models/map_layers.py new file mode 100644 index 00000000..1d2d0356 --- /dev/null +++ b/uvdat/core/models/map_layers.py @@ -0,0 +1,98 @@ +import json +from pathlib import Path +import tempfile + +from django.core.files.base import ContentFile +from django.db import models +from django_extensions.db.models import TimeStampedModel +import large_image +from s3_file_field import S3FileField + +from .file_item import FileItem + + +class AbstractMapLayer(TimeStampedModel): + file_item = models.ForeignKey(FileItem, on_delete=models.CASCADE, null=True) + metadata = models.JSONField(blank=True, null=True) + default_style = models.JSONField(blank=True, null=True) + index = models.IntegerField(null=True) + + def is_in_context(self, context_id): + return self.file_item.is_in_context(context_id) + + class Meta: + abstract = True + + +class RasterMapLayer(AbstractMapLayer): + cloud_optimized_geotiff = S3FileField() + + def get_image_data(self, resolution: float = 1.0): + with tempfile.TemporaryDirectory() as tmp: + raster_path = Path(tmp, 'raster') + with open(raster_path, 'wb') as raster_file: + raster_file.write(self.cloud_optimized_geotiff.read()) + source = large_image.open(raster_path) + data, data_format = source.getRegion(format='numpy') + data = data[:, :, 0] + if resolution != 1.0: + step = int(1 / resolution) + data = data[::step][::step] + return data.tolist() + + +class VectorMapLayer(AbstractMapLayer): + geojson_file = S3FileField(null=True) + + def write_geojson_data(self, content: str | dict): + if isinstance(content, str): + data = content + elif isinstance(content, dict): + data = json.dumps(content) + else: + raise Exception(f'Invalid content type supplied: {type(content)}') + + self.geojson_file.save('vectordata.geojson', ContentFile(data.encode())) + + def read_geojson_data(self) -> dict: + """Read and load the data from geojson_file into a dict.""" + return json.load(self.geojson_file.open()) + + def get_tile_extents(self): + """Return a dict that maps z tile values to the x/y extent at that depth.""" + return { + entry.pop('z'): entry + for entry in ( + VectorTile.objects.filter(map_layer=self) + .values('z') + .annotate( + min_x=models.Min('x'), + min_y=models.Min('y'), + max_x=models.Max('x'), + max_y=models.Max('y'), + ) + .order_by() + ) + } + + +class VectorTile(models.Model): + EMPTY_TILE_DATA = { + 'type': 'FeatureCollection', + 'features': [], + } + + map_layer = models.ForeignKey(VectorMapLayer, on_delete=models.CASCADE) + geojson_data = models.JSONField(blank=True, null=True) + x = models.IntegerField(default=0) + y = models.IntegerField(default=0) + z = models.IntegerField(default=0) + + class Meta: + constraints = [ + # Ensure that a full index only ever resolves to one record + models.UniqueConstraint( + name='unique-map-layer-index', fields=['map_layer', 'z', 'x', 'y'] + ) + ] + indexes = [models.Index(fields=('z', 'x', 'y'), name='vectortile-coordinates-index')] diff --git a/uvdat/core/models/networks.py b/uvdat/core/models/networks.py new file mode 100644 index 00000000..11248c7d --- /dev/null +++ b/uvdat/core/models/networks.py @@ -0,0 +1,44 @@ +from django.contrib.gis.db import models as geo_models +from django.db import models + +from .dataset import Dataset + + +class NetworkNode(models.Model): + name = models.CharField(max_length=255, unique=True) + dataset = models.ForeignKey(Dataset, on_delete=models.CASCADE, related_name='network_nodes') + metadata = models.JSONField(blank=True, null=True) + capacity = models.IntegerField(null=True) + location = geo_models.PointField() + + def is_in_context(self, context_id): + return self.dataset.is_in_context(context_id) + + def get_adjacent_nodes(self) -> models.QuerySet: + entering_node_ids = ( + NetworkEdge.objects.filter(to_node=self.id) + .values_list('from_node_id', flat=True) + .distinct() + ) + exiting_node_ids = ( + NetworkEdge.objects.filter(from_node=self.id) + .values_list('to_node_id', flat=True) + .distinct() + ) + return NetworkNode.objects.exclude(id=self.id).filter( + models.Q(id__in=entering_node_ids) | models.Q(id__in=exiting_node_ids) + ) + + +class NetworkEdge(models.Model): + name = models.CharField(max_length=255, unique=True) + dataset = models.ForeignKey(Dataset, on_delete=models.CASCADE, related_name='network_edges') + metadata = models.JSONField(blank=True, null=True) + capacity = models.IntegerField(null=True) + line_geometry = geo_models.LineStringField() + directed = models.BooleanField(default=False) + from_node = models.ForeignKey(NetworkNode, related_name='+', on_delete=models.CASCADE) + to_node = models.ForeignKey(NetworkNode, related_name='+', on_delete=models.CASCADE) + + def is_in_context(self, context_id): + return self.dataset.is_in_context(context_id) diff --git a/uvdat/core/models/regions.py b/uvdat/core/models/regions.py new file mode 100644 index 00000000..3d79ea43 --- /dev/null +++ b/uvdat/core/models/regions.py @@ -0,0 +1,62 @@ +from django.contrib.gis.db import models as geo_models +from django.db import models + +from .context import Context +from .dataset import Dataset +from .map_layers import VectorMapLayer + + +class SourceRegion(models.Model): + name = models.CharField(max_length=255) + dataset = models.ForeignKey(Dataset, on_delete=models.CASCADE, related_name='regions') + metadata = models.JSONField(blank=True, null=True) + boundary = geo_models.MultiPolygonField() + + def is_in_context(self, context_id): + return self.dataset.is_in_context(context_id) + + class Meta: + constraints = [ + # We enforce name uniqueness across datasets + models.UniqueConstraint(name='unique-source-region-name', fields=['dataset', 'name']) + ] + + +class DerivedRegion(models.Model): + class VectorOperation(models.TextChoices): + UNION = 'UNION', 'Union' + INTERSECTION = 'INTERSECTION', 'Intersection' + + name = models.CharField(max_length=255) + context = models.ForeignKey(Context, on_delete=models.CASCADE, related_name='derived_regions') + metadata = models.JSONField(blank=True, null=True) + boundary = geo_models.MultiPolygonField() + + # Data from the source regions + source_regions = models.ManyToManyField(SourceRegion, related_name='derived_regions') + operation = models.CharField( + max_length=max(len(choice[0]) for choice in VectorOperation.choices), + choices=VectorOperation.choices, + ) + + # Since these regions are not associated with Datasets, + # They need their own reference to a map representation + map_layer = models.ForeignKey(VectorMapLayer, on_delete=models.PROTECT) + + def is_in_context(self, context_id): + return self.context.id == int(context_id) + + def get_map_layers(self): + return [ + { + 'id': self.map_layer.id, + 'index': 0, + 'type': 'vector', + } + ] + + class Meta: + constraints = [ + # We enforce name uniqueness across contexts + models.UniqueConstraint(name='unique-derived-region-name', fields=['context', 'name']) + ] diff --git a/uvdat/core/models/simulations.py b/uvdat/core/models/simulations.py new file mode 100644 index 00000000..2d9bee05 --- /dev/null +++ b/uvdat/core/models/simulations.py @@ -0,0 +1,107 @@ +from django.db import models +from django_extensions.db.models import TimeStampedModel + +from uvdat.core.tasks import simulations as uvdat_simulations + +from .context import Context +from .dataset import Dataset +from .map_layers import RasterMapLayer, VectorMapLayer + + +class SimulationResult(TimeStampedModel): + class SimulationType(models.TextChoices): + FLOOD_1 = 'FLOOD_1', 'Flood Scenario 1' + RECOVERY = 'RECOVERY', 'Recovery Scenario' + + simulation_type = models.CharField( + max_length=max(len(choice[0]) for choice in SimulationType.choices), + choices=SimulationType.choices, + ) + context = models.ForeignKey( + Context, on_delete=models.CASCADE, related_name='simulation_results' + ) + input_args = models.JSONField(blank=True, null=True) + output_data = models.JSONField(blank=True, null=True) + error_message = models.TextField(null=True, blank=True) + + def is_in_context(self, context_id): + return self.context.id == int(context_id) + + def get_simulation_type(self): + if not self.simulation_type or self.simulation_type not in AVAILABLE_SIMULATIONS: + raise ValueError(f'Simulation type not found: {self.simulation_type}') + return AVAILABLE_SIMULATIONS[self.simulation_type] + + def get_name(self): + # method built into text choice field + simulation_type = self.get_simulation_type_display() + return simulation_type + + def run(self, **kwargs): + self.output_data = None + self.save() + simulation_type = self.get_simulation_type() + simulation_type['func'].delay(self.id, **kwargs) + + +AVAILABLE_SIMULATIONS = { + 'FLOOD_1': { + 'description': """ + Provide a network dataset, elevation dataset, and flood dataset + to determine which network nodes go out of service + when the target flood occurs. + """, + 'output_type': 'node_animation', + 'func': uvdat_simulations.flood_scenario_1, + 'args': [ + { + 'name': 'network_dataset', + 'type': Dataset, + 'options_annotations': { + 'network_nodes_count': models.Count('network_nodes'), + 'network_edges_count': models.Count('network_edges'), + }, + 'options_query': { + 'network_nodes_count__gte': 1, + 'network_edges_count__gte': 1, + }, + }, + { + 'name': 'elevation_data', + 'type': RasterMapLayer, + 'options_query': {'file_item__dataset__category': 'elevation'}, + }, + { + 'name': 'flood_area', + 'type': VectorMapLayer, + 'options_query': {'file_item__dataset__category': 'flood'}, + }, + ], + }, + 'RECOVERY': { + 'description': """ + Provide the output of another simulation which returns a list of deactivated nodes, + and select a recovery mode to determine the order in which + nodes will come back online. + """, + 'output_type': 'node_animation', + 'func': uvdat_simulations.recovery_scenario, + 'args': [ + { + 'name': 'node_failure_simulation_result', + 'type': SimulationResult, + 'options_query': { + 'simulation_type__in': [ + 'FLOOD_1', + # add other node failure simulation types here as created + ] + }, + }, + { + 'name': 'recovery_mode', + 'type': str, + 'options': uvdat_simulations.NODE_RECOVERY_MODES, + }, + ], + }, +} diff --git a/uvdat/core/rest/__init__.py b/uvdat/core/rest/__init__.py new file mode 100644 index 00000000..88706ca7 --- /dev/null +++ b/uvdat/core/rest/__init__.py @@ -0,0 +1,17 @@ +from .chart import ChartViewSet +from .context import ContextViewSet +from .dataset import DatasetViewSet +from .map_layers import RasterMapLayerViewSet, VectorMapLayerViewSet +from .regions import DerivedRegionViewSet, SourceRegionViewSet +from .simulations import SimulationViewSet + +__all__ = [ + ContextViewSet, + ChartViewSet, + RasterMapLayerViewSet, + VectorMapLayerViewSet, + DatasetViewSet, + SourceRegionViewSet, + DerivedRegionViewSet, + SimulationViewSet, +] diff --git a/uvdat/core/rest/chart.py b/uvdat/core/rest/chart.py new file mode 100644 index 00000000..794b3373 --- /dev/null +++ b/uvdat/core/rest/chart.py @@ -0,0 +1,42 @@ +from django.http import HttpResponse +from rest_framework.decorators import action +from rest_framework.viewsets import GenericViewSet, mixins + +from uvdat.core.models import Chart + +from .serializers import ChartSerializer + + +class ChartViewSet(GenericViewSet, mixins.ListModelMixin): + queryset = Chart.objects.all() + serializer_class = ChartSerializer + + def get_queryset(self, **kwargs): + context_id = kwargs.get('context') + if context_id: + return Chart.objects.filter(context__id=context_id) + return Chart.objects.all() + + def validate_editable(self, chart, func, *args, **kwargs): + if chart.editable: + return func(*args, **kwargs) + else: + return HttpResponse('Not an editable chart.', status=400) + + @action(detail=True, methods=['post']) + def new_line(self, request, **kwargs): + chart = self.get_object() + self.validate_editable(chart, chart.new_line) + return HttpResponse(status=200) + + @action(detail=True, methods=['post']) + def rename_lines(self, request, **kwargs): + chart = self.get_object() + self.validate_editable(chart, chart.rename_lines, new_names=request.data) + return HttpResponse(status=200) + + @action(detail=True, methods=['post']) + def clear(self, request, **kwargs): + chart = self.get_object() + self.validate_editable(chart, chart.clear) + return HttpResponse(status=200) diff --git a/uvdat/core/rest/context.py b/uvdat/core/rest/context.py new file mode 100644 index 00000000..58ffbf00 --- /dev/null +++ b/uvdat/core/rest/context.py @@ -0,0 +1,23 @@ +from django.http import HttpResponse +from rest_framework.decorators import action +from rest_framework.viewsets import ModelViewSet + +from uvdat.core.models import Context +from uvdat.core.rest.serializers import ContextSerializer + + +class ContextViewSet(ModelViewSet): + queryset = Context.objects.all() + serializer_class = ContextSerializer + + @action(detail=True, methods=['get']) + def regions(self, request, **kwargs): + context = self.get_object() + regions = context.derived_regions.all() + return HttpResponse(regions, status=200) + + @action(detail=True, methods=['get']) + def simulation_results(self, request, **kwargs): + context = self.get_object() + simulation_results = context.simulation_results.all() + return HttpResponse(simulation_results, status=200) diff --git a/uvdat/core/rest/dataset.py b/uvdat/core/rest/dataset.py new file mode 100644 index 00000000..05eb7a3b --- /dev/null +++ b/uvdat/core/rest/dataset.py @@ -0,0 +1,84 @@ +import json + +from django.http import HttpResponse +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.viewsets import ModelViewSet + +from uvdat.core.models import Dataset +from uvdat.core.rest import serializers as uvdat_serializers +from uvdat.core.tasks.chart import add_gcc_chart_datum + + +class DatasetViewSet(ModelViewSet): + serializer_class = uvdat_serializers.DatasetSerializer + + def get_queryset(self): + context_id = self.request.query_params.get('context') + if context_id: + return Dataset.objects.filter(context__id=context_id) + else: + return Dataset.objects.all() + + @action(detail=True, methods=['get']) + def map_layers(self, request, **kwargs): + dataset: Dataset = self.get_object() + map_layers = list(dataset.get_map_layers().select_related('file_item__dataset')) + + # Set serializer based on dataset type + if dataset.dataset_type == Dataset.DatasetType.RASTER: + serializer = uvdat_serializers.RasterMapLayerSerializer(map_layers, many=True) + elif dataset.dataset_type == Dataset.DatasetType.VECTOR: + # Inject tile extents + extents = dataset.get_map_layer_tile_extents() + for layer in map_layers: + layer.tile_extents = extents.pop(layer.id) + + # Set serializer + serializer = uvdat_serializers.ExtendedVectorMapLayerSerializer(map_layers, many=True) + else: + raise NotImplementedError(f'Dataset Type {dataset.dataset_type}') + + # Return response with rendered data + return Response(serializer.data, status=200) + + @action(detail=True, methods=['get']) + def convert(self, request, **kwargs): + dataset = self.get_object() + dataset.spawn_conversion_task() + return HttpResponse(status=200) + + @action(detail=True, methods=['get']) + def network(self, request, **kwargs): + dataset = self.get_object() + network = dataset.get_network() + return HttpResponse( + json.dumps( + { + 'nodes': [ + uvdat_serializers.NetworkNodeSerializer(n).data + for n in network.get('nodes') + ], + 'edges': [ + uvdat_serializers.NetworkEdgeSerializer(e).data + for e in network.get('edges') + ], + } + ), + status=200, + ) + + @action(detail=True, methods=['get']) + def gcc(self, request, **kwargs): + dataset = self.get_object() + context_id = request.query_params.get('context') + exclude_nodes = request.query_params.get('exclude_nodes', []) + exclude_nodes = exclude_nodes.split(',') + exclude_nodes = [int(n) for n in exclude_nodes if len(n)] + excluded_node_names = [ + n.name for n in dataset.get_network().get('nodes') if n.id in exclude_nodes + ] + + gcc = dataset.get_network_gcc(exclude_nodes) + add_gcc_chart_datum(dataset, context_id, excluded_node_names, len(gcc)) + return HttpResponse(json.dumps(gcc), status=200) diff --git a/uvdat/core/rest/map_layers.py b/uvdat/core/rest/map_layers.py new file mode 100644 index 00000000..9eb27961 --- /dev/null +++ b/uvdat/core/rest/map_layers.py @@ -0,0 +1,57 @@ +import json + +from django.http import HttpResponse +from django_large_image.rest import LargeImageFileDetailMixin +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.viewsets import ModelViewSet + +from uvdat.core.models import RasterMapLayer, VectorMapLayer +from uvdat.core.models.map_layers import VectorTile +from uvdat.core.rest.serializers import ( + RasterMapLayerSerializer, + VectorMapLayerDetailSerializer, + VectorMapLayerSerializer, +) + + +class RasterMapLayerViewSet(ModelViewSet, LargeImageFileDetailMixin): + queryset = RasterMapLayer.objects.select_related('file_item__dataset').all() + serializer_class = RasterMapLayerSerializer + FILE_FIELD_NAME = 'cloud_optimized_geotiff' + + @action( + detail=True, + methods=['get'], + url_path=r'raster-data/(?P[\d*\.?\d*]+)', + url_name='raster_data', + ) + def get_raster_data(self, request, resolution: str = '1', **kwargs): + raster_map_layer = self.get_object() + data = raster_map_layer.get_image_data(float(resolution)) + return HttpResponse(json.dumps(data), status=200) + + +class VectorMapLayerViewSet(ModelViewSet): + queryset = VectorMapLayer.objects.select_related('file_item__dataset').all() + serializer_class = VectorMapLayerSerializer + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + serializer = VectorMapLayerDetailSerializer(instance) + return Response(serializer.data) + + @action( + detail=True, + methods=['get'], + url_path=r'tiles/(?P\d+)/(?P\d+)/(?P\d+)', + url_name='tiles', + ) + def get_vector_tile(self, request, x: str, y: str, z: str, pk: str): + # Return vector tile or empty tile + try: + tile = VectorTile.objects.get(map_layer_id=pk, x=x, y=y, z=z) + except VectorTile.DoesNotExist: + return Response(VectorTile.EMPTY_TILE_DATA, status=200) + + return Response(tile.geojson_data, status=200) diff --git a/uvdat/core/rest/regions.py b/uvdat/core/rest/regions.py new file mode 100644 index 00000000..d752e8db --- /dev/null +++ b/uvdat/core/rest/regions.py @@ -0,0 +1,68 @@ +import json + +from django.http import HttpResponse +from drf_yasg.utils import swagger_auto_schema +from rest_framework.decorators import action +from rest_framework.viewsets import GenericViewSet, mixins + +from uvdat.core.models import DerivedRegion, SourceRegion +from uvdat.core.tasks.regions import DerivedRegionCreationError, create_derived_region + +from .serializers import ( + DerivedRegionCreationSerializer, + DerivedRegionDetailSerializer, + DerivedRegionListSerializer, + SourceRegionSerializer, +) + + +class SourceRegionViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet): + queryset = SourceRegion.objects.all() + serializer_class = SourceRegionSerializer + + +class DerivedRegionViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet): + queryset = DerivedRegion.objects.all() + serializer_class = DerivedRegionListSerializer + + def get_serializer_class(self): + if self.detail: + return DerivedRegionDetailSerializer + + return super().get_serializer_class() + + def get_queryset(self): + context_id = self.request.query_params.get('context') + if context_id: + return DerivedRegion.objects.filter(context__id=context_id) + else: + return DerivedRegion.objects.all() + + @action(detail=True, methods=['GET']) + def as_feature(self, request, *args, **kwargs): + obj: DerivedRegion = self.get_object() + feature = { + 'type': 'Feature', + 'geometry': json.loads(obj.boundary.geojson), + 'properties': DerivedRegionListSerializer(instance=obj).data, + } + + return HttpResponse(json.dumps(feature)) + + @swagger_auto_schema(request_body=DerivedRegionCreationSerializer) + def create(self, request, *args, **kwargs): + serializer = DerivedRegionCreationSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + try: + data = serializer.validated_data + derived_region = create_derived_region( + name=data['name'], + context=data['context'], + region_ids=data['regions'], + operation=data['operation'], + ) + except DerivedRegionCreationError as e: + return HttpResponse(str(e), status=400) + + return HttpResponse(DerivedRegionDetailSerializer(instance=derived_region).data, status=201) diff --git a/uvdat/core/rest/serializers.py b/uvdat/core/rest/serializers.py new file mode 100644 index 00000000..b9413ba4 --- /dev/null +++ b/uvdat/core/rest/serializers.py @@ -0,0 +1,203 @@ +import json + +from django.contrib.gis.serializers import geojson +from rest_framework import serializers + +from uvdat.core.models import ( + Chart, + Context, + Dataset, + DerivedRegion, + FileItem, + NetworkEdge, + NetworkNode, + RasterMapLayer, + SimulationResult, + SourceRegion, + VectorMapLayer, +) + + +class ContextSerializer(serializers.ModelSerializer): + default_map_center = serializers.SerializerMethodField('get_center') + + def get_center(self, obj): + # Web client expects Lon, Lat + if obj.default_map_center: + return [obj.default_map_center.y, obj.default_map_center.x] + + class Meta: + model = Context + fields = '__all__' + + +class DatasetSerializer(serializers.ModelSerializer): + class Meta: + model = Dataset + fields = '__all__' + + +class FileItemSerializer(serializers.ModelSerializer): + class Meta: + model = FileItem + fields = '__all__' + + +class ChartSerializer(serializers.ModelSerializer): + class Meta: + model = Chart + fields = '__all__' + + +class AbstractMapLayerSerializer(serializers.Serializer): + name = serializers.SerializerMethodField('get_name') + type = serializers.SerializerMethodField('get_type') + dataset_id = serializers.SerializerMethodField('get_dataset_id') + file_item = serializers.SerializerMethodField('get_file_item') + + def get_name(self, obj: VectorMapLayer | RasterMapLayer): + if obj.file_item is None: + return None + return obj.file_item.name + + def get_type(self, obj: VectorMapLayer | RasterMapLayer): + if isinstance(obj, VectorMapLayer): + return 'vector' + return 'raster' + + def get_dataset_id(self, obj: VectorMapLayer | RasterMapLayer): + if obj.file_item and obj.file_item.dataset: + return obj.file_item.dataset.id + return None + + def get_file_item(self, obj: VectorMapLayer | RasterMapLayer): + if obj.file_item is None: + return None + return { + 'id': obj.file_item.id, + 'name': obj.file_item.name, + } + + +class RasterMapLayerSerializer(serializers.ModelSerializer, AbstractMapLayerSerializer): + class Meta: + model = RasterMapLayer + fields = '__all__' + + +class ExtendedVectorMapLayerSerializer(serializers.ModelSerializer, AbstractMapLayerSerializer): + tile_extents = serializers.JSONField() + + class Meta: + model = VectorMapLayer + exclude = ['geojson_file'] + + +class VectorMapLayerSerializer(serializers.ModelSerializer, AbstractMapLayerSerializer): + class Meta: + model = VectorMapLayer + exclude = ['geojson_file'] + + +class VectorMapLayerDetailSerializer(serializers.ModelSerializer, AbstractMapLayerSerializer): + derived_region_id = serializers.SerializerMethodField('get_derived_region_id') + tile_extents = serializers.SerializerMethodField('get_tile_extents') + + def get_derived_region_id(self, obj): + dr = obj.derivedregion_set.first() + if dr is None: + return None + return dr.id + + def get_tile_extents(self, obj: VectorMapLayer): + return obj.get_tile_extents() + + class Meta: + model = VectorMapLayer + exclude = ['geojson_file'] + + +class SourceRegionSerializer(serializers.ModelSerializer): + class Meta: + model = SourceRegion + fields = '__all__' + + +class RegionFeatureCollectionSerializer(geojson.Serializer): + # Override this method to ensure the pk field is a number instead of a string + def get_dump_object(self, obj): + val = super().get_dump_object(obj) + val['properties']['id'] = int(val['properties'].pop('pk')) + + return val + + +class DerivedRegionListSerializer(serializers.ModelSerializer): + map_layers = serializers.SerializerMethodField('get_map_layers') + + def get_map_layers(self, obj): + return obj.get_map_layers() + + class Meta: + model = DerivedRegion + fields = [ + 'id', + 'name', + 'context', + 'metadata', + 'source_regions', + 'operation', + 'map_layers', + ] + + +class DerivedRegionDetailSerializer(serializers.ModelSerializer): + class Meta: + model = DerivedRegion + fields = '__all__' + + boundary = serializers.SerializerMethodField() + map_layers = serializers.SerializerMethodField('get_map_layers') + + def get_boundary(self, obj): + return json.loads(obj.boundary.geojson) + + def get_map_layers(self, obj): + return obj.get_map_layers() + + +class DerivedRegionCreationSerializer(serializers.ModelSerializer): + class Meta: + model = DerivedRegion + fields = [ + 'name', + 'context', + 'regions', + 'operation', + ] + + regions = serializers.ListField(child=serializers.IntegerField()) + operation = serializers.ChoiceField(choices=DerivedRegion.VectorOperation.choices) + + +class NetworkNodeSerializer(serializers.ModelSerializer): + class Meta: + model = NetworkNode + fields = '__all__' + + +class NetworkEdgeSerializer(serializers.ModelSerializer): + class Meta: + model = NetworkEdge + fields = '__all__' + + +class SimulationResultSerializer(serializers.ModelSerializer): + name = serializers.SerializerMethodField('get_name') + + def get_name(self, obj): + return obj.get_name() + + class Meta: + model = SimulationResult + fields = '__all__' diff --git a/uvdat/core/rest/simulations.py b/uvdat/core/rest/simulations.py new file mode 100644 index 00000000..2372092e --- /dev/null +++ b/uvdat/core/rest/simulations.py @@ -0,0 +1,115 @@ +import inspect +import json +import re + +from django.http import HttpResponse +from rest_framework.decorators import action +from rest_framework.serializers import ModelSerializer +from rest_framework.viewsets import GenericViewSet + +from uvdat.core.models import Context +from uvdat.core.models.simulations import AVAILABLE_SIMULATIONS, SimulationResult +import uvdat.core.rest.serializers as uvdat_serializers + + +# TODO: Refactor +def get_available_simulations(context_id: int): + sims = [] + for index, (name, details) in enumerate(AVAILABLE_SIMULATIONS.items()): + details = details.copy() + details['description'] = re.sub(r'\n\s+', ' ', details['description']) + args = [] + for a in details['args']: + options = a.get('options') + if not options: + options_annotations = a.get('options_annotations') + options_query = a.get('options_query') + options_type = a.get('type') + option_serializer_matches = [ + s + for name, s in inspect.getmembers(uvdat_serializers, inspect.isclass) + if issubclass(s, ModelSerializer) + and s.Meta.model == options_type + and 'Extended' not in s.__name__ + ] + if not options_query or not options_type or len(option_serializer_matches) == 0: + options = [] + else: + option_serializer = option_serializer_matches[0] + option_objects = options_type.objects + if options_annotations: + option_objects = option_objects.annotate(**options_annotations) + options = list( + option_serializer(d).data + for d in option_objects.filter( + **options_query, + ).all() + if d.is_in_context(context_id) + ) + args.append( + { + 'name': a['name'], + 'options': options, + } + ) + details['args'] = args + del details['func'] + details['id'] = index + details['name'] = SimulationResult.SimulationType[name].label + sims.append(details) + return sims + + +class SimulationViewSet(GenericViewSet): + serializer_class = uvdat_serializers.SimulationResultSerializer + + @action( + detail=False, + methods=['get'], + url_path=r'available/context/(?P[\d*]+)', + ) + def list_available(self, request, context_id: int, **kwargs): + sims = get_available_simulations(context_id) + return HttpResponse( + json.dumps(sims), + status=200, + ) + + @action( + detail=False, + methods=['get'], + url_path=r'(?P[\d*]+)/context/(?P[\d*]+)/results', + ) + def list_results(self, request, simulation_index: int, context_id: int, **kwargs): + simulation_type = list(AVAILABLE_SIMULATIONS.keys())[int(simulation_index)] + return HttpResponse( + json.dumps( + list( + uvdat_serializers.SimulationResultSerializer(s).data + for s in SimulationResult.objects.filter( + simulation_type=simulation_type, context__id=context_id + ).all() + ) + ), + status=200, + ) + + @action( + detail=False, + methods=['post'], + url_path=r'run/(?P[\d*]+)/context/(?P[\d*]+)', + ) + def run(self, request, simulation_index: int, context_id: int, **kwargs): + simulation_type = list(AVAILABLE_SIMULATIONS.keys())[int(simulation_index)] + context = Context.objects.get(id=context_id) + input_args = request.data + sim_result = SimulationResult.objects.create( + simulation_type=simulation_type, + input_args=input_args, + context=context, + ) + sim_result.run(**input_args) + return HttpResponse( + json.dumps(uvdat_serializers.SimulationResultSerializer(sim_result).data), + status=200, + ) diff --git a/uvdat/core/serializers.py b/uvdat/core/serializers.py deleted file mode 100644 index 9b75dc0c..00000000 --- a/uvdat/core/serializers.py +++ /dev/null @@ -1,104 +0,0 @@ -import json - -from django.contrib.gis.serializers import geojson -from rest_framework import serializers - -from uvdat.core.models import Chart, City, Dataset, DerivedRegion, NetworkNode, SimulationResult -from uvdat.core.tasks.simulations import AVAILABLE_SIMULATIONS - - -class NetworkNodeSerializer(serializers.ModelSerializer): - location = serializers.SerializerMethodField('get_location') - - def get_location(self, obj): - if obj.location: - return [obj.location.y, obj.location.x] - else: - return [0, 0] - - class Meta: - model = NetworkNode - fields = '__all__' - - -class ChartSerializer(serializers.ModelSerializer): - class Meta: - model = Chart - fields = '__all__' - - -class DatasetSerializer(serializers.ModelSerializer): - class Meta: - model = Dataset - fields = '__all__' - - -class CitySerializer(serializers.ModelSerializer): - datasets = DatasetSerializer(many=True) - center = serializers.SerializerMethodField('get_center') - - def get_center(self, obj): - if obj.center: - return [obj.center.y, obj.center.x] - else: - return [0, 0] - - class Meta: - model = City - fields = '__all__' - - -class SimulationResultSerializer(serializers.ModelSerializer): - name = serializers.SerializerMethodField('get_name') - - def get_name(self, obj): - time = obj.modified.strftime('%Y-%m-%d %H:%M') - simulation_type_matches = [t for t in AVAILABLE_SIMULATIONS if t['id'] == obj.simulation_id] - if len(simulation_type_matches) == 0: - return f'Result {time}' - else: - simulation_type = simulation_type_matches[0] - return f"{simulation_type['name']} Result {time}" - - class Meta: - model = SimulationResult - - -class RegionFeatureCollectionSerializer(geojson.Serializer): - # Override this method to ensure the pk field is a number instead of a string - def get_dump_object(self, obj): - val = super().get_dump_object(obj) - val["properties"]["id"] = int(val["properties"].pop("pk")) - - return val - - -class DerivedRegionListSerializer(serializers.ModelSerializer): - class Meta: - model = DerivedRegion - fields = ['id', 'name', 'city', 'properties', 'source_regions', 'source_operation'] - - -class DerivedRegionDetailSerializer(serializers.ModelSerializer): - class Meta: - model = DerivedRegion - fields = '__all__' - - boundary = serializers.SerializerMethodField() - - def get_boundary(self, obj): - return json.loads(obj.boundary.geojson) - - -class DerivedRegionCreationSerializer(serializers.ModelSerializer): - class Meta: - model = DerivedRegion - fields = [ - 'name', - 'city', - 'regions', - 'operation', - ] - - regions = serializers.ListField(child=serializers.IntegerField()) - operation = serializers.ChoiceField(choices=DerivedRegion.VectorOperation.choices) diff --git a/uvdat/core/tasks/charts.py b/uvdat/core/tasks/chart.py similarity index 72% rename from uvdat/core/tasks/charts.py rename to uvdat/core/tasks/chart.py index ea57f581..8804874b 100644 --- a/uvdat/core/tasks/charts.py +++ b/uvdat/core/tasks/chart.py @@ -1,27 +1,32 @@ from datetime import datetime +from celery import shared_task import pandas from webcolors import name_to_hex -from uvdat.core.models import Chart +from uvdat.core.models import Chart, Context -def convert_chart_data(chart): - options = chart.style.get('options') - chart_options = options.get('chart') - label_column = chart_options.get('labels') - dataset_columns = chart_options.get('datasets') - palette_options = options.get('palette') +@shared_task +def convert_chart(chart_id, conversion_options): + chart = Chart.objects.get(id=chart_id) + + label_column = conversion_options.get('labels') + dataset_columns = conversion_options.get('datasets') + palette_options = conversion_options.get('palette') chart_data = { 'labels': [], 'datasets': [], } - if chart.raw_data_type == 'csv': - raw_data = pandas.read_csv(chart.raw_data_file.open()) + chart_file = chart.fileitem_set.first() + if chart_file.file_type == 'csv': + raw_data = pandas.read_csv(chart_file.file.open()) else: - raise NotImplementedError(f'Convert chart data for raw data type {chart.raw_data_type}') + raise NotImplementedError( + f'Convert chart data for file type {chart_file.file_type}', + ) chart_data['labels'] = raw_data[label_column].fillna(-1).tolist() chart_data['datasets'] = [ { @@ -35,24 +40,23 @@ def convert_chart_data(chart): chart.chart_data = chart_data chart.save() - print(f"Saved data for chart {chart.name}") + print(f'\t Saved converted data for chart {chart.name}.') -def get_gcc_chart(dataset): +def get_gcc_chart(dataset, context_id): chart_name = f'{dataset.name} Greatest Connected Component Sizes' try: return Chart.objects.get(name=chart_name) except Chart.DoesNotExist: - chart = Chart( + chart = Chart.objects.create( name=chart_name, description=""" A set of previously-run calculations for the network's greatest connected component (GCC), showing GCC size by number of excluded nodes """, - city=dataset.city, - category="gcc", - clearable=True, + context=Context.objects.get(id=context_id), + editable=True, chart_data={}, metadata=[], chart_options={ @@ -62,12 +66,13 @@ def get_gcc_chart(dataset): 'y_range': [0, dataset.network_nodes.count()], }, ) + print('\t', f'Chart {chart.name} created.') chart.save() return chart -def add_gcc_chart_datum(dataset, excluded_node_names, gcc_size): - chart = get_gcc_chart(dataset) +def add_gcc_chart_datum(dataset, context_id, excluded_node_names, gcc_size): + chart = get_gcc_chart(dataset, context_id) if len(chart.metadata) == 0: # no data exists, need to initialize data structures chart.metadata = [] @@ -99,7 +104,7 @@ def add_gcc_chart_datum(dataset, excluded_node_names, gcc_size): chart.chart_data['datasets'] = datasets new_entry = { - 'run_time': datetime.now().strftime("%d/%m/%Y %H:%M"), + 'run_time': datetime.now().strftime('%d/%m/%Y %H:%M'), 'n_excluded_nodes': len(excluded_node_names), 'excluded_node_names': excluded_node_names, 'gcc_size': gcc_size, diff --git a/uvdat/core/tasks/conversion.py b/uvdat/core/tasks/conversion.py deleted file mode 100644 index de1be1b4..00000000 --- a/uvdat/core/tasks/conversion.py +++ /dev/null @@ -1,203 +0,0 @@ -import json -import os -from pathlib import Path -import tempfile -import zipfile - -from celery import shared_task -from django.core.files.base import ContentFile -from geojson2vt import geojson2vt, vt2geojson -import geopandas -import large_image_converter -import numpy -import rasterio -import shapefile - -from uvdat.core.models import Dataset -from uvdat.core.tasks.networks import save_network_nodes -from uvdat.core.tasks.regions import save_regions -from uvdat.core.utils import add_styling - - -@shared_task -def convert_raw_data(dataset_id): - dataset = Dataset.objects.get(id=dataset_id) - if dataset.raw_data_type == 'cloud_optimized_geotiff': - convert_cog(dataset) - elif dataset.raw_data_type == 'geojson': - convert_geojson(dataset) - elif dataset.raw_data_type == 'shape_file_archive': - convert_shape_file_archive(dataset) - else: - print(f'\t\tNo op for raw data type {dataset.raw_data_type}') - - if dataset.network: - save_network_nodes(dataset) - if dataset.category == 'region': - save_regions(dataset) - - dataset.processing = False - dataset.save() - - -def convert_cog(dataset): - """Saves the raster_file attribute of the dataset.""" - with tempfile.TemporaryDirectory() as temp_dir: - raw_data_path = Path(temp_dir, 'raw_data.tiff') - raster_path = Path(temp_dir, 'raster.tiff') - cog_raster_path = Path(temp_dir, 'cog_raster.tiff') - - transparency_threshold = None - trim_distribution_percentage = None - if dataset.style and dataset.style.get('options'): - options = dataset.style.get('options') - transparency_threshold = options.get('transparency_threshold', transparency_threshold) - trim_distribution_percentage = options.get( - 'trim_distribution_percentage', trim_distribution_percentage - ) - - with open(raw_data_path, 'wb') as raw_data: - with dataset.raw_data_archive.open('rb') as raw_data_archive: - raw_data.write(raw_data_archive.read()) - - with open(raw_data_path, 'rb') as raw_data: - input_data = rasterio.open(raw_data) - output_data = rasterio.open( - raster_path, - 'w', - driver='GTiff', - height=input_data.height, - width=input_data.width, - count=1, - dtype=numpy.float32, - crs=input_data.crs, - transform=input_data.transform, - ) - band = input_data.read(1) - - if trim_distribution_percentage: - # trim a number of values from both ends of the distribution - histogram, bin_edges = numpy.histogram(band, bins=1000) - trim_n = band.size * trim_distribution_percentage - new_min = None - new_max = None - sum_values = 0 - for bin_index, bin_count in enumerate(histogram): - bin_edge = bin_edges[bin_index] - sum_values += bin_count - if new_min is None and sum_values > trim_n: - new_min = bin_edge - if new_max is None and sum_values > band.size - trim_n: - new_max = bin_edge - if new_min: - band[band < new_min] = new_min - if new_max: - band[band > new_max] = new_max - - if transparency_threshold is not None: - band[band < transparency_threshold] = transparency_threshold - - band_range = [float(band.min()), float(band.max())] - dataset.style['data_range'] = band_range - - output_data.write(band, 1) - output_data.close() - - large_image_converter.convert(str(raster_path), str(cog_raster_path)) - with open(cog_raster_path, 'rb') as raster_file: - dataset.raster_file.save(cog_raster_path, ContentFile(raster_file.read())) - - print(f'\t Raster conversion complete for {dataset.name}.') - dataset.processing = False - dataset.save() - - -def convert_geojson(dataset, geodata_path=None): - """Saves the vector_tiles_file and geodata_file attributes of the dataset.""" - with tempfile.TemporaryDirectory() as temp_dir: - if geodata_path is None: - geodata_path = Path(temp_dir, 'geo.json') - with open(geodata_path, 'wb') as geodata_file: - original_data = dataset.raw_data_archive.open('rb').read() - original_data = json.loads(original_data) - original_data['features'] = add_styling(original_data['features'], dataset.style) - geodata_file.write(json.dumps(original_data).encode()) - - original_projection = original_data.get('crs', {}).get('properties', {}).get('name') - if original_projection: - geodata = geopandas.read_file(geodata_path) - geodata = geodata.set_crs(original_projection, allow_override=True) - geodata = geodata.to_crs(4326) - geodata.to_file(geodata_path) - with open(geodata_path, 'rb') as geodata_file: - contents = geodata_file.read() - dataset.geodata_file.save(geodata_path, ContentFile(contents)) - geojson_size = os.path.getsize(geodata_path) - geojson_mb = geojson_size >> 20 - if geojson_mb > 100: - print( - f'\t Found large GeoJSON data ({geojson_mb} MB). Creating tiled GeoJSON for rendering...' - ) - tile_geojson(dataset, geodata_path) - - -def tile_geojson(dataset, geodata_path=None): - with tempfile.TemporaryDirectory() as temp_dir: - tiled_geo_path = Path(temp_dir, 'tiled_geo.json') - tiled_geo = {} - with open(geodata_path, 'rb') as geodata_file: - contents = geodata_file.read() - # convert to tiles and save to vector_tiles_file - tile_index = geojson2vt.geojson2vt( - json.loads(contents.decode()), - {'indexMaxZoom': 12, 'maxZoom': 12, 'indexMaxPoints': 0}, - ) - for coord in tile_index.tile_coords: - tile = tile_index.get_tile(coord['z'], coord['x'], coord['y']) - features = tile.get('features') - if features and len(features) > 0: - if not coord['z'] in tiled_geo: - tiled_geo[coord['z']] = {} - if not coord['x'] in tiled_geo[coord['z']]: - tiled_geo[coord['z']][coord['x']] = {} - - tiled_geo[coord['z']][coord['x']][coord['y']] = vt2geojson.vt2geojson(tile) - - with open(tiled_geo_path, 'w') as tiled_geo_file: - json.dump(tiled_geo, tiled_geo_file) - with open(tiled_geo_path, 'rb') as tiled_geo_file: - dataset.vector_tiles_file.save( - tiled_geo_path, - ContentFile(tiled_geo_file.read()), - ) - print(f'\t GeoJSON to Tiles conversion complete for {dataset.name}.') - - -def convert_shape_file_archive(dataset): - features = [] - original_projection = None - with tempfile.TemporaryDirectory() as temp_dir: - archive_path = Path(temp_dir, 'archive.zip') - geodata_path = Path(temp_dir, 'geo.json') - - with open(archive_path, 'wb') as archive_file: - archive_file.write(dataset.raw_data_archive.open('rb').read()) - with zipfile.ZipFile(archive_path) as zip_archive: - filenames = zip_archive.namelist() - for filename in filenames: - if filename.endswith('.shp'): - sf = shapefile.Reader(f'{archive_path}/{filename}') - features.extend(sf.__geo_interface__['features']) - if filename.endswith('.prj'): - original_projection = zip_archive.open(filename).read().decode() - - features = add_styling(features, dataset.style) - with open(geodata_path, 'w') as geodata_file: - json.dump({'type': 'FeatureCollection', 'features': features}, geodata_file) - geodata = geopandas.read_file(geodata_path) - geodata = geodata.set_crs(original_projection, allow_override=True) - geodata = geodata.to_crs(4326) - geodata.to_file(geodata_path) - print(f'\t Shapefile to GeoJSON conversion complete for {dataset.name}.') - - convert_geojson(dataset, geodata_path=geodata_path) diff --git a/uvdat/core/tasks/dataset.py b/uvdat/core/tasks/dataset.py new file mode 100644 index 00000000..2bcfb125 --- /dev/null +++ b/uvdat/core/tasks/dataset.py @@ -0,0 +1,64 @@ +from celery import shared_task + +from uvdat.core.models import ( + Dataset, + FileItem, + NetworkEdge, + NetworkNode, + RasterMapLayer, + SourceRegion, + VectorMapLayer, +) + +from .map_layers import create_raster_map_layer, create_vector_map_layer, save_vector_tiles +from .networks import create_network +from .regions import create_source_regions + + +@shared_task +def convert_dataset( + dataset_id, + style_options=None, + network_options=None, + region_options=None, +): + dataset = Dataset.objects.get(id=dataset_id) + dataset.processing = True + dataset.save() + + # Determine network/region classificaton + network = dataset.classification == Dataset.Classification.NETWORK + region = dataset.classification == Dataset.Classification.REGION + + if dataset.dataset_type == dataset.DatasetType.RASTER: + RasterMapLayer.objects.filter(file_item__dataset=dataset).delete() + for file_to_convert in FileItem.objects.filter(dataset=dataset): + create_raster_map_layer( + file_to_convert, + style_options=style_options, + ) + + elif dataset.dataset_type == dataset.DatasetType.VECTOR: + VectorMapLayer.objects.filter(file_item__dataset=dataset).delete() + if network: + NetworkNode.objects.filter(dataset=dataset).delete() + NetworkEdge.objects.filter(dataset=dataset).delete() + elif region: + SourceRegion.objects.filter(dataset=dataset).delete() + + for file_to_convert in FileItem.objects.filter(dataset=dataset): + vector_map_layer = create_vector_map_layer( + file_to_convert, + style_options=style_options, + ) + if network: + create_network(vector_map_layer, network_options) + elif region_options: + create_source_regions(vector_map_layer, region_options) + + # Create vector tiles after geojson_data may have + # been altered by create_network or create_source_regions + save_vector_tiles(vector_map_layer) + + dataset.processing = False + dataset.save() diff --git a/uvdat/core/tasks/map_layers.py b/uvdat/core/tasks/map_layers.py new file mode 100644 index 00000000..1faa717f --- /dev/null +++ b/uvdat/core/tasks/map_layers.py @@ -0,0 +1,192 @@ +import json +from pathlib import Path +import tempfile +import zipfile + +from django.core.files.base import ContentFile +from geojson2vt import geojson2vt, vt2geojson +import geopandas +import large_image_converter +import numpy +import rasterio +import shapefile +from webcolors import name_to_hex + +from uvdat.core.models import RasterMapLayer, VectorMapLayer, VectorTile + + +def add_styling(geojson_data, style_options): + if not style_options: + return geojson_data + + outline = style_options.get('outline') + palette = style_options.get('palette') + color_property = style_options.get('color_property') + color_delimiter = style_options.get('color_delimiter', ',') + + features = [] + for index, feature in enumerate(geojson_data.iterfeatures()): + feature_colors = [] + if color_property: + color_value = feature['properties'].get(color_property) + if color_value: + feature_colors += str(color_value).split(color_delimiter) + + if isinstance(palette, dict): + feature_colors = [palette[c] for c in feature_colors if c in palette] + elif isinstance(palette, list): + feature_colors.append(palette[index % len(palette)]) + + if outline: + feature_colors.append(outline) + + feature_colors = [name_to_hex(c) for c in feature_colors] + feature['properties']['colors'] = ','.join(feature_colors) + features.append(feature) + return geopandas.GeoDataFrame.from_features(features) + + +def create_raster_map_layer(file_item, style_options): + """Save a RasterMapLayer from a FileItem's contents.""" + new_map_layer = RasterMapLayer.objects.create( + file_item=file_item, + metadata={}, + default_style=style_options, + index=file_item.index, + ) + print('\t', f'RasterMapLayer {new_map_layer.id} created.') + + with tempfile.TemporaryDirectory() as temp_dir: + raw_data_path = Path(temp_dir, 'raw_data.tiff') + with open(raw_data_path, 'wb') as raw_data: + with file_item.file.open('rb') as raw_data_archive: + raw_data.write(raw_data_archive.read()) + + transparency_threshold = style_options.get('transparency_threshold') + trim_distribution_percentage = style_options.get('trim_distribution_percentage') + + raster_path = Path(temp_dir, 'raster.tiff') + with open(raw_data_path, 'rb') as raw_data: + input_data = rasterio.open(raw_data) + output_data = rasterio.open( + raster_path, + 'w', + driver='GTiff', + height=input_data.height, + width=input_data.width, + count=1, + dtype=numpy.float32, + crs=input_data.crs, + transform=input_data.transform, + ) + band = input_data.read(1) + + if trim_distribution_percentage: + # trim a number of values from both ends of the distribution + histogram, bin_edges = numpy.histogram(band, bins=1000) + trim_n = band.size * trim_distribution_percentage + new_min = None + new_max = None + sum_values = 0 + for bin_index, bin_count in enumerate(histogram): + bin_edge = bin_edges[bin_index] + sum_values += bin_count + if new_min is None and sum_values > trim_n: + new_min = bin_edge + if new_max is None and sum_values > band.size - trim_n: + new_max = bin_edge + if new_min: + band[band < new_min] = new_min + if new_max: + band[band > new_max] = new_max + + if transparency_threshold is not None: + band[band < transparency_threshold] = transparency_threshold + + band_range = [float(band.min()), float(band.max())] + new_map_layer.default_style['data_range'] = band_range + + output_data.write(band, 1) + output_data.close() + + cog_raster_path = Path(temp_dir, 'cog_raster.tiff') + large_image_converter.convert(str(raster_path), str(cog_raster_path)) + with open(cog_raster_path, 'rb') as cog_raster_file: + new_map_layer.cloud_optimized_geotiff.save( + cog_raster_path, ContentFile(cog_raster_file.read()) + ) + + return new_map_layer + + +def create_vector_map_layer(file_item, style_options): + """Save a VectorMapLayer from a FileItem's contents.""" + new_map_layer = VectorMapLayer.objects.create( + file_item=file_item, + metadata={}, + default_style=style_options, + index=file_item.index, + ) + print('\t', f'VectorMapLayer {new_map_layer.id} created.') + + if file_item.file_type == 'zip': + geojson_data = convert_zip_to_geojson(file_item) + elif file_item.file_type == 'geojson' or file_item.file_type == 'json': + source_data = json.load(file_item.file.open()) + source_projection = source_data.get('crs', {}).get('properties', {}).get('name') + geojson_data = geopandas.GeoDataFrame.from_features(source_data.get('features')) + if source_projection: + geojson_data = geojson_data.set_crs(source_projection) + geojson_data = geojson_data.to_crs(4326) + + geojson_data = add_styling(geojson_data, style_options) + new_map_layer.write_geojson_data(geojson_data.to_json()) + new_map_layer.save() + + # save_vector_tiles(vector_map_layer=new_map_layer) + + return new_map_layer + + +def convert_zip_to_geojson(file_item): + features = [] + source_projection = None + with tempfile.TemporaryDirectory() as temp_dir: + archive_path = Path(temp_dir, 'archive.zip') + with open(archive_path, 'wb') as archive_file: + archive_file.write(file_item.file.open('rb').read()) + with zipfile.ZipFile(archive_path) as zip_archive: + filenames = zip_archive.namelist() + for filename in filenames: + if filename.endswith('.shp'): + sf = shapefile.Reader(f'{archive_path}/{filename}') + features.extend(sf.__geo_interface__['features']) + if filename.endswith('.prj'): + source_projection = zip_archive.open(filename).read().decode() + geodata = geopandas.GeoDataFrame.from_features(features) + geodata = geodata.set_crs(source_projection, allow_override=True) + geodata = geodata.to_crs(4326) + return geodata + + +def save_vector_tiles(vector_map_layer): + tile_index = geojson2vt.geojson2vt( + vector_map_layer.read_geojson_data(), + {'indexMaxZoom': 12, 'maxZoom': 12, 'indexMaxPoints': 0}, + ) + + created = 0 + for coord in tile_index.tile_coords: + tile = tile_index.get_tile(coord['z'], coord['x'], coord['y']) + features = tile.get('features') + if features and len(features) > 0: + VectorTile.objects.create( + map_layer=vector_map_layer, + geojson_data=vt2geojson.vt2geojson(tile), + x=coord['x'], + y=coord['y'], + z=coord['z'], + ) + created += 1 + + print('\t', f'{created} vector tiles created.') diff --git a/uvdat/core/tasks/networks.py b/uvdat/core/tasks/networks.py index 7731f2a3..a63be8da 100644 --- a/uvdat/core/tasks/networks.py +++ b/uvdat/core/tasks/networks.py @@ -1,13 +1,12 @@ -from pathlib import Path -import tempfile +import json -from django.contrib.gis.geos import Point +from django.contrib.gis.geos import LineString, Point import geopandas import networkx as nx import numpy import shapely -from uvdat.core.models import NetworkNode +from uvdat.core.models import NetworkEdge, NetworkNode NODE_RECOVERY_MODES = [ 'random', @@ -21,139 +20,208 @@ ] -def save_network_nodes(dataset): - with tempfile.TemporaryDirectory() as temp_dir: - geodata_path = Path(temp_dir, 'geo.json') - with open(geodata_path, 'wb') as geodata_file: - geodata_file.write(dataset.geodata_file.open('rb').read()) - geodata = geopandas.read_file(geodata_path) - - connection_column = None - connection_column_delimiter = None - node_id_column = None - if dataset.metadata: - connection_column = dataset.metadata.get('connection_column') - connection_column_delimiter = dataset.metadata.get('connection_column_delimiter') - node_id_column = dataset.metadata.get('node_id_column') - if connection_column is None or connection_column not in geodata.columns: - raise ValueError( - f'This dataset does not specify a valid \ - "connection_column" in its options. Column options are {geodata.columns}.' - ) - if connection_column_delimiter is None: - raise ValueError( - 'This dataset does not specify a "connection_column_delimiter" in its options.' - ) - if node_id_column is None or node_id_column not in geodata.columns: - raise ValueError( - f'This dataset does not specify a valid \ - "node_id_column" in its options. Column options are {geodata.columns}.' - ) - - geodata = geodata.copy() - geodata[connection_column].fillna('', inplace=True) - edge_set = geodata[geodata.geom_type != 'Point'] - node_set = geodata[geodata.geom_type == 'Point'] - - adjacencies = {} - total_adjacencies = 0 - unique_routes = node_set[connection_column].drop_duplicates() - unique_routes = unique_routes[~unique_routes.str.contains(connection_column_delimiter)] - for unique_route in unique_routes: - nodes = node_set[node_set[connection_column].str.contains(unique_route, regex=False)] - edges = edge_set[edge_set[connection_column].str.contains(unique_route, regex=False)] - - # create one route line from all edges in this group - if len(edges) < 1: - continue - route = edges.unary_union - if route.geom_type == 'MultiLineString': - route = shapely.ops.linemerge(route) - route = shapely.extract_unique_points(route.segmentize(10)) - route_points = geopandas.GeoDataFrame(geometry=list(route.geoms)).set_crs(node_set.crs) - - # convert both nodes and route to a projected crs - # for better accuracy of the sjoin_nearest function to follow - nodes = nodes.to_crs(3857) - route_points = route_points.to_crs(3857) - - # along the points of the route, find the nodes that are nearest (in order of the route points) - route_points_nearest_nodes = route_points.sjoin_nearest(nodes).sort_index() - route_nodes = list( - route_points_nearest_nodes.drop_duplicates(subset=[node_id_column])[node_id_column] - ) - - # print(unique_route, route_nodes) - - # record adjacencies from the ordered route nodes list - for i in range(len(route_nodes) - 1): - current_node_id = route_nodes[i] - adjacent_node_id = route_nodes[i + 1] - - if current_node_id not in adjacencies: - adjacencies[current_node_id] = [] - if adjacent_node_id not in adjacencies: - adjacencies[adjacent_node_id] = [] - - if adjacent_node_id not in adjacencies[current_node_id]: - adjacencies[current_node_id].append(adjacent_node_id) - total_adjacencies += 1 - if current_node_id not in adjacencies[adjacent_node_id]: - adjacencies[adjacent_node_id].append(current_node_id) - - # print('total connections=', total_adjacencies) - - # Create all NetworkNode objects first, then populate adjacencies after. - dataset.network_nodes.all().delete() - node_set = node_set.drop_duplicates(subset=[node_id_column]) - for i, node in node_set.iterrows(): - properties = node.drop(['geometry', 'colors', node_id_column]) - properties = properties.replace(numpy.nan, 'None') - location = Point(x=node.geometry.x, y=node.geometry.y) - NetworkNode( - name=node[node_id_column], - dataset=dataset, - location=location, - properties=dict(properties), - ).save() - for i, node in node_set.iterrows(): - adjacent_node_ids = adjacencies.get(node[node_id_column]) - if not adjacent_node_ids: - continue - node_object = NetworkNode.objects.get(name=node[node_id_column]) - node_object.adjacent_nodes.set(NetworkNode.objects.filter(name__in=adjacent_node_ids)) - node_object.save() - - -def construct_edge_list(dataset): - network_nodes = dataset.network_nodes.values_list('id', flat=True) - edges = NetworkNode.adjacent_nodes.through.objects.filter( - from_networknode_id__in=network_nodes, to_networknode_id__in=network_nodes - ).values_list('from_networknode_id', 'to_networknode_id') - +def create_network(vector_map_layer, network_options): + connection_column = network_options.get('connection_column') + connection_column_delimiter = network_options.get('connection_column_delimiter') + node_id_column = network_options.get('node_id_column') + + source_data = vector_map_layer.read_geojson_data() + geodata = geopandas.GeoDataFrame.from_features(source_data.get('features')).set_crs(4326) + geodata[connection_column].fillna('', inplace=True) + edge_set = geodata[geodata.geom_type != 'Point'] + node_set = geodata[geodata.geom_type == 'Point'] + + unique_routes = node_set[connection_column].drop_duplicates() + unique_routes = unique_routes[~unique_routes.str.contains(connection_column_delimiter)] + for unique_route in unique_routes: + nodes = node_set[node_set[connection_column].str.contains(unique_route, regex=False)] + edges = edge_set[edge_set[connection_column].str.contains(unique_route, regex=False)] + + # create one route line from all edges in this group + if len(edges) < 1: + continue + route = edges.unary_union + if route.geom_type == 'MultiLineString': + route = shapely.ops.linemerge(route) + route = shapely.extract_unique_points(route.segmentize(10)) + route_points = geopandas.GeoDataFrame(geometry=list(route.geoms)).set_crs(node_set.crs) + + # convert both nodes and route to a projected crs + # for better accuracy of the sjoin_nearest function to follow + nodes_reprojected = nodes.copy().to_crs(3857) + route_points_reprojected = route_points.copy().to_crs(3857) + + # along the points of the route, find the nodes that are nearest + # (in order of the route points) + route_points_nearest_nodes = route_points_reprojected.sjoin_nearest( + nodes_reprojected, distance_col='distance' + ) + + # find cutoff points where one edge geometry stops and another begins + cutoff_points = {} + for nearest_node, point_group in route_points_nearest_nodes.groupby(node_id_column): + cutoff_point = point_group.sort_values(by=['distance']).iloc[0] + cutoff_points[nearest_node] = cutoff_point['geometry'] + + # use ordered node names to create NetworkNode objects + # and cutoff points to create NetworkEdge objects + route_nodes = route_points_nearest_nodes.drop_duplicates( + subset=[node_id_column] + ).reset_index() + for i, current_node in route_nodes.iterrows(): + current_node_name = current_node[node_id_column] + # refer back to node_set for coords with original projection + current_node_coordinates = node_set.loc[ + node_set[node_id_column] == current_node_name + ].iloc[0]['geometry'] + + try: + from_node_obj = NetworkNode.objects.get( + dataset=vector_map_layer.file_item.dataset, + name=current_node_name, + ) + except NetworkNode.DoesNotExist: + from_node_obj = NetworkNode.objects.create( + dataset=vector_map_layer.file_item.dataset, + name=current_node_name, + location=Point( + current_node_coordinates.x, + current_node_coordinates.y, + ), + metadata={ + k: v + for k, v in current_node.to_dict().items() + if k not in ['index', 'geometry', 'index_right', 'distance'] + and str(v).lower() != 'nan' + }, + ) + + if i < len(route_nodes) - 1: + next_node = route_nodes.iloc[i + 1] + next_node_name = next_node[node_id_column] + # refer back to node_set for coords with original projection + next_node_coordinates = node_set.loc[ + node_set[node_id_column] == next_node_name + ].iloc[0]['geometry'] + + try: + to_node_obj = NetworkNode.objects.get( + dataset=vector_map_layer.file_item.dataset, + name=next_node_name, + ) + except NetworkNode.DoesNotExist: + to_node_obj = NetworkNode.objects.create( + dataset=vector_map_layer.file_item.dataset, + name=next_node_name, + location=Point( + next_node_coordinates.x, + next_node_coordinates.y, + ), + metadata={ + k: v + for k, v in next_node.to_dict().items() + if k not in ['index', 'geometry', 'index_right', 'distance'] + and str(v).lower() != 'nan' + }, + ) + + route_points_start_index = route_points_reprojected.index[ + route_points_reprojected['geometry'] == cutoff_points[current_node_name] + ][0] + route_points_end_index = route_points_reprojected.index[ + route_points_reprojected['geometry'] == cutoff_points[next_node_name] + ][0] + + edge_points = route_points[ # use original projection + route_points_start_index : route_points_end_index + 1 # +1 to include end + ] + edge_line_geometry = LineString(*[Point(p.x, p.y) for p in edge_points['geometry']]) + + try: + NetworkEdge.objects.get( + dataset=vector_map_layer.file_item.dataset, + name=f'{current_node_name} - {next_node_name}', + ) + except NetworkEdge.DoesNotExist: + metadata = json.loads( + json.dumps( + edge_set.loc[edge_set[connection_column] == unique_route] + .loc[:, edge_set.columns != 'geometry'] + .iloc[0] + .fillna('') + .to_dict() + ) + ) + NetworkEdge.objects.create( + dataset=vector_map_layer.file_item.dataset, + name=f'{current_node_name} - {next_node_name}', + from_node=from_node_obj, + to_node=to_node_obj, + line_geometry=edge_line_geometry, + metadata=metadata, + ) + + total_nodes = 0 + total_edges = 0 + new_feature_set = [] + # rewrite vector_map_layer geojson_data with updated features + for n in NetworkNode.objects.filter(dataset=vector_map_layer.file_item.dataset): + node_as_feature = { + 'id': i, + 'type': 'Feature', + 'geometry': { + 'type': 'Point', + 'coordinates': n.location.coords, + }, + 'properties': dict(node_id=n.id, **n.metadata), + } + new_feature_set.append(node_as_feature) + total_nodes += 1 + + for e in NetworkEdge.objects.filter(dataset=vector_map_layer.file_item.dataset): + edge_as_feature = { + 'id': i, + 'type': 'Feature', + 'geometry': { + 'type': 'LineString', + 'coordinates': e.line_geometry.coords, + }, + 'properties': dict( + edge_id=e.id, + from_node_id=e.from_node.id, + to_node_id=e.to_node.id, + **e.metadata, + ), + } + new_feature_set.append(edge_as_feature) + total_edges += 1 + + new_geodata = geopandas.GeoDataFrame.from_features(new_feature_set) + vector_map_layer.write_geojson_data(new_geodata.to_json()) + vector_map_layer.metadata['network'] = True + vector_map_layer.save() + print('\t', f'{total_nodes} nodes and {total_edges} edges created.') + + +def get_dataset_network_graph(dataset): + db_representation = dataset.get_network() # Construct adj list edge_list: dict[int, list[int]] = {} - for start, end in edges: - if start not in edge_list: - edge_list[start] = [] - - edge_list[start].append(end) - - # Ensure that the type of all keys is an integer - assert all(isinstance(x, int) for x in edge_list.keys()) - - # Sort all node id lists - for start_node in edge_list.keys(): - edge_list[start_node].sort() - - return edge_list + for e in db_representation.get('edges'): + if e.from_node.id not in edge_list: + edge_list[e.from_node.id] = [] + edge_list[e.from_node.id].append(e.to_node.id) + for edge_id in edge_list.keys(): + edge_list[edge_id].sort() + graph_representation = nx.from_dict_of_lists(edge_list) + return graph_representation -def network_gcc(edges: dict[int, list[int]], exclude_nodes: list[int]) -> list[int]: +def get_dataset_network_gcc(dataset, exclude_nodes: list[int]) -> list[int]: # Create graph, remove nodes, get GCC - G = nx.from_dict_of_lists(edges) - G.remove_nodes_from(exclude_nodes) - gcc = max(nx.connected_components(G), key=len) + graph = dataset.get_network_graph() + graph.remove_nodes_from(exclude_nodes) + gcc = max(nx.connected_components(graph), key=len) # Return GCC's list of nodes return list(gcc) @@ -162,29 +230,29 @@ def network_gcc(edges: dict[int, list[int]], exclude_nodes: list[int]) -> list[i # Authored by Jack Watson # Takes in a second argument, measure, which is a string specifying the centrality # measure to calculate. -def sort_graph_centrality(G, measure): +def sort_graph_centrality(g, measure): if measure == 'betweenness': - cent = nx.betweenness_centrality(G) # get betweenness centrality + cent = nx.betweenness_centrality(g) # get betweenness centrality elif measure == 'degree': - cent = nx.degree_centrality(G) + cent = nx.degree_centrality(g) elif measure == 'information': - cent = nx.current_flow_closeness_centrality(G) + cent = nx.current_flow_closeness_centrality(g) elif measure == 'eigenvector': - cent = nx.eigenvector_centrality(G, 10000) + cent = nx.eigenvector_centrality(g, 10000) elif measure == 'load': - cent = nx.load_centrality(G) + cent = nx.load_centrality(g) elif measure == 'closeness': - cent = nx.closeness_centrality(G) + cent = nx.closeness_centrality(g) elif measure == 'second order': - cent = nx.second_order_centrality(G) + cent = nx.second_order_centrality(g) cent_list = list(cent.items()) # convert to np array cent_arr = numpy.array(cent_list) cent_idx = numpy.argsort(cent_arr, 0) # sort array of tuples by betweenness # cent_sorted = cent_arr[cent_idx[:, 1]] - node_list = list(G.nodes()) + node_list = list(g.nodes()) nodes_sorted = [node_list[i] for i in cent_idx[:, 1]] - edge_list = list(G.edges()) + edge_list = list(g.edges()) # Currently sorted from lowest to highest betweenness; let's reverse that nodes_sorted.reverse() diff --git a/uvdat/core/tasks/regions.py b/uvdat/core/tasks/regions.py index eef104df..49a80571 100644 --- a/uvdat/core/tasks/regions.py +++ b/uvdat/core/tasks/regions.py @@ -5,30 +5,31 @@ from django.contrib.gis.db.models.aggregates import Union from django.contrib.gis.geos import GEOSGeometry from django.db import transaction +import geopandas -from uvdat.core.models import DerivedRegion, Region +from uvdat.core.models import Context, DerivedRegion, SourceRegion, VectorMapLayer +from uvdat.core.tasks.map_layers import save_vector_tiles -class DerivedRegionCreationException(Exception): +class DerivedRegionCreationError(Exception): pass -def create_derived_region(name: str, city_id: int, region_ids: List[int], operation: str): +def create_derived_region(name: str, context: Context, region_ids: List[int], operation: str): # Ensure at least two regions provided - source_regions = Region.objects.filter(pk__in=region_ids) + source_regions = SourceRegion.objects.filter(pk__in=region_ids) if source_regions.count() < 2: - raise DerivedRegionCreationException("Derived Regions must consist of multiple regions") + raise DerivedRegionCreationError('Derived Regions must consist of multiple regions') - # Ensure all regions are from one city - source_cities = list((source_regions.values_list('city', flat=True).distinct())) - if len(source_cities) > 1: - raise DerivedRegionCreationException( - f"Multiple cities included in source regions: {source_cities}" + # Ensure all regions are from one context + if any(not sr.is_in_context(context.id) for sr in source_regions): + raise DerivedRegionCreationError( + f'Source Regions must exist in the same context with id {context.id}.' ) # Only handle union operations for now if operation == DerivedRegion.VectorOperation.INTERSECTION: - raise DerivedRegionCreationException("Intersection Operation not yet supported") + raise DerivedRegionCreationError('Intersection Operation not yet supported') # Simply include all multipolygons from all source regions # Convert Polygon to MultiPolygon if necessary @@ -42,35 +43,45 @@ def create_derived_region(name: str, city_id: int, region_ids: List[int], operat # Check for duplicate derived regions existing = list( - DerivedRegion.objects.filter(city=city_id, boundary=GEOSGeometry(new_boundary)).values_list( - 'id', flat=True - ) + DerivedRegion.objects.filter( + context=context, boundary=GEOSGeometry(new_boundary) + ).values_list('id', flat=True) ) if existing: - raise DerivedRegionCreationException( - f"Derived Regions with identical boundary already exist: {existing}" + raise DerivedRegionCreationError( + f'Derived Regions with identical boundary already exist: {existing}' ) # Save and return with transaction.atomic(): + new_map_layer = VectorMapLayer.objects.create( + metadata={}, + default_style={}, + index=0, + ) + new_map_layer.write_geojson_data(geojson) + new_map_layer.save() + save_vector_tiles(new_map_layer) + derived_region = DerivedRegion.objects.create( name=name, - city=city_id, - properties={}, + context=context, + metadata={}, boundary=new_boundary, - source_operation=operation, + operation=operation, + map_layer=new_map_layer, ) derived_region.source_regions.set(source_regions) return derived_region -def save_regions(dataset): - dataset.regions.all().delete() - property_map = dataset.style.get('property_map') - name_property = property_map.get('name') if property_map else None +def create_source_regions(vector_map_layer, region_options): + name_property = region_options.get('name_property') + geodata = vector_map_layer.read_geojson_data() - geodata = json.loads(dataset.geodata_file.read().decode()) + region_count = 0 + new_feature_set = [] for feature in geodata['features']: properties = feature['properties'] geometry = feature['geometry'] @@ -86,13 +97,30 @@ def save_regions(dataset): geometry['coordinates'] = [geometry['coordinates']] # Create region with properties and MultiPolygon - region = Region( + region = SourceRegion( name=name, boundary=GEOSGeometry(str(geometry)), - properties=properties, - dataset=dataset, - city=dataset.city, + metadata=properties, + dataset=vector_map_layer.file_item.dataset, ) region.save() + region_count += 1 + + properties['region_id'] = region.id + properties['region_name'] = region.name + properties['dataset_id'] = region.dataset.id + new_feature_set.append( + { + 'id': region.id, + 'type': 'Feature', + 'geometry': geometry, + 'properties': properties, + } + ) - print(f"Saved regions for {dataset.name}") + # Save updated features to layer + new_geodata = geopandas.GeoDataFrame.from_features(new_feature_set).set_crs(3857) + new_geodata.to_crs(4326) + vector_map_layer.write_geojson_data(new_geodata.to_json()) + vector_map_layer.save() + print('\t', f'{region_count} regions created.') diff --git a/uvdat/core/tasks/simulations.py b/uvdat/core/tasks/simulations.py index 585d8c00..e926206d 100644 --- a/uvdat/core/tasks/simulations.py +++ b/uvdat/core/tasks/simulations.py @@ -1,9 +1,5 @@ -import inspect -import json -import networkx as nx from pathlib import Path import random -import re import tempfile from celery import shared_task @@ -11,21 +7,19 @@ import large_image import shapely -from rest_framework.serializers import ModelSerializer -from uvdat.core.models import City, Dataset, SimulationResult +from uvdat.core.models import Dataset from uvdat.core.tasks.networks import ( NODE_RECOVERY_MODES, - construct_edge_list, + get_dataset_network_graph, sort_graph_centrality, ) -import uvdat.core.serializers as serializers -def get_network_node_elevations(network_nodes, elevation_dataset): +def get_network_node_elevations(network_nodes, elevation_data): with tempfile.TemporaryDirectory() as tmp: raster_path = Path(tmp, 'raster') with open(raster_path, 'wb') as raster_file: - raster_file.write(elevation_dataset.raster_file.read()) + raster_file.write(elevation_data.cloud_optimized_geotiff.read()) source = large_image.open(raster_path) data, data_format = source.getRegion(format='numpy') data = data[:, :, 0] @@ -49,21 +43,23 @@ def get_network_node_elevations(network_nodes, elevation_dataset): @shared_task -def flood_scenario_1(simulation_result_id, network_dataset, elevation_dataset, flood_dataset): +def flood_scenario_1(simulation_result_id, network_dataset, elevation_data, flood_area): + from uvdat.core.models import RasterMapLayer, SimulationResult, VectorMapLayer + result = SimulationResult.objects.get(id=simulation_result_id) try: network_dataset = Dataset.objects.get(id=network_dataset) - elevation_dataset = Dataset.objects.get(id=elevation_dataset) - flood_dataset = Dataset.objects.get(id=flood_dataset) + elevation_data = RasterMapLayer.objects.get(id=elevation_data) + flood_area = VectorMapLayer.objects.get(id=flood_area) except Dataset.DoesNotExist: result.error_message = 'Dataset not found.' result.save() return if ( - not network_dataset.network - or elevation_dataset.category != 'elevation' - or flood_dataset.category != 'flood' + not network_dataset.get_network() + or elevation_data.file_item.dataset.category != 'elevation' + or flood_area.file_item.dataset.category != 'flood' ): result.error_message = 'Invalid dataset selected.' result.save() @@ -71,7 +67,7 @@ def flood_scenario_1(simulation_result_id, network_dataset, elevation_dataset, f node_failures = [] network_nodes = network_dataset.network_nodes.all() - flood_geodata = json.loads(flood_dataset.geodata_file.open().read().decode()) + flood_geodata = flood_area.read_geojson_data() flood_areas = [ shapely.geometry.shape(feature['geometry']) for feature in flood_geodata['features'] ] @@ -80,7 +76,7 @@ def flood_scenario_1(simulation_result_id, network_dataset, elevation_dataset, f if any(flood_area.contains(node_point) for flood_area in flood_areas): node_failures.append(network_node) - node_elevations = get_network_node_elevations(network_nodes, elevation_dataset) + node_elevations = get_network_node_elevations(network_nodes, elevation_data) node_failures.sort(key=lambda n: node_elevations[n.id]) result.output_data = {'node_failures': [n.id for n in node_failures]} @@ -89,6 +85,8 @@ def flood_scenario_1(simulation_result_id, network_dataset, elevation_dataset, f @shared_task def recovery_scenario(simulation_result_id, node_failure_simulation_result, recovery_mode): + from uvdat.core.models import SimulationResult + result = SimulationResult.objects.get(id=simulation_result_id) try: node_failure_simulation_result = SimulationResult.objects.get( @@ -115,9 +113,8 @@ def recovery_scenario(simulation_result_id, node_failure_simulation_result, reco result.error_message = 'Dataset not found.' result.save() return - edge_list = construct_edge_list(dataset) - G = nx.from_dict_of_lists(edge_list) - nodes_sorted, edge_list = sort_graph_centrality(G, recovery_mode) + graph = get_dataset_network_graph(dataset) + nodes_sorted, edge_list = sort_graph_centrality(graph, recovery_mode) node_recoveries.sort(key=lambda n: nodes_sorted.index(n)) result.output_data = { @@ -125,116 +122,3 @@ def recovery_scenario(simulation_result_id, node_failure_simulation_result, reco 'node_recoveries': node_recoveries, } result.save() - - -AVAILABLE_SIMULATIONS = [ - { - 'id': 1, - 'name': 'Flood Scenario 1', - 'description': ''' - Provide a network dataset, elevation dataset, and flood dataset - to determine which network nodes go out of service - when the target flood occurs. - ''', - 'output_type': 'node_animation', - 'func': flood_scenario_1, - 'args': [ - { - 'name': 'network_dataset', - 'type': Dataset, - 'options_query': {'network': True}, - }, - { - 'name': 'elevation_dataset', - 'type': Dataset, - 'options_query': {'category': 'elevation'}, - }, - { - 'name': 'flood_dataset', - 'type': Dataset, - 'options_query': {'category': 'flood'}, - }, - ], - }, - { - 'id': 2, - 'name': 'Recovery Scenario', - 'description': ''' - Provide the output of another simulation which returns a list of deactivated nodes, - and select a recovery mode to determine the order in which - nodes will come back online. - ''', - 'output_type': 'node_animation', - 'func': recovery_scenario, - 'args': [ - { - 'name': 'node_failure_simulation_result', - 'type': SimulationResult, - 'options_query': {'simulation_id__in': [1]}, - }, - { - 'name': 'recovery_mode', - 'type': str, - 'options': NODE_RECOVERY_MODES, - }, - ], - }, -] - - -def get_available_simulations(city_id: int): - sims = [] - for available in AVAILABLE_SIMULATIONS: - available = available.copy() - available['description'] = re.sub(r'\n\s+', ' ', available['description']) - args = [] - for a in available['args']: - options = a.get('options') - if not options: - options_query = a.get('options_query') - options_type = a.get('type') - option_serializer_matches = [ - s - for name, s in inspect.getmembers(serializers, inspect.isclass) - if issubclass(s, ModelSerializer) and s.Meta.model == options_type - ] - if not options_query or not options_type or len(option_serializer_matches) == 0: - options = [] - else: - option_serializer = option_serializer_matches[0] - if hasattr(options_type, 'city'): - options_query['city__id'] = city_id - options = list( - option_serializer(d).data - for d in options_type.objects.filter( - **options_query, - ).all() - ) - args.append( - { - 'name': a['name'], - 'options': options, - } - ) - available['args'] = args - del available['func'] - sims.append(available) - return sims - - -def run_simulation(simulation_id: int, city_id: int, **kwargs): - city = City.objects.get(id=city_id) - simulation_matches = [s for s in AVAILABLE_SIMULATIONS if s['id'] == simulation_id] - if len(simulation_matches) > 0: - sim_result, created = SimulationResult.objects.get_or_create( - simulation_id=simulation_id, - input_args=kwargs, - city=city, - ) - sim_result.output_data = None - sim_result.save() - - simulation = simulation_matches[0] - simulation['func'].delay(sim_result.id, **kwargs) - return serializers.SimulationResultSerializer(sim_result).data - return f"No simulation found with id {simulation_id}." diff --git a/uvdat/core/tests/test_populate.py b/uvdat/core/tests/test_populate.py new file mode 100644 index 00000000..601b0e07 --- /dev/null +++ b/uvdat/core/tests/test_populate.py @@ -0,0 +1,46 @@ +from django.core.management import call_command +import pytest + +from uvdat.core.models import ( + Chart, + Context, + Dataset, + DerivedRegion, + FileItem, + NetworkEdge, + NetworkNode, + RasterMapLayer, + SimulationResult, + SourceRegion, + VectorMapLayer, + VectorTile, +) + + +@pytest.mark.django_db +def test_populate(): + # smaller subset for faster evaluation + # 0 is MBTA Rapid Transit, tests network eval + # 4 is Massachusetts Elevation Data, tests raster eval + # 5 is Boston Neighborhoods, tests regions eval + # 8 is Boston Sea Level Rises, tests multi-map-layer dataset eval + dataset_indexes = [0, 4, 5, 8] + + call_command( + 'populate', + include_large=True, + dataset_indexes=dataset_indexes, + ) + + assert Chart.objects.all().count() == 1 + assert Context.objects.all().count() == 3 + assert Dataset.objects.all().count() == 4 + assert DerivedRegion.objects.all().count() == 0 + assert FileItem.objects.all().count() == 7 + assert NetworkEdge.objects.all().count() == 164 + assert NetworkNode.objects.all().count() == 158 + assert RasterMapLayer.objects.all().count() == 1 + assert SimulationResult.objects.all().count() == 0 + assert SourceRegion.objects.all().count() == 24 + assert VectorMapLayer.objects.all().count() == 5 + assert VectorTile.objects.all().count() == 135 diff --git a/uvdat/core/utils.py b/uvdat/core/utils.py deleted file mode 100644 index 7d02fa22..00000000 --- a/uvdat/core/utils.py +++ /dev/null @@ -1,33 +0,0 @@ -from webcolors import name_to_hex - - -def add_styling(features, style_dict): - if not style_dict: - return features - - property_map = style_dict.get('property_map') or {} - options = style_dict.get('options') or {} - outline = options.get('outline') - palette = options.get('palette') - color_delimiter = options.get('color_delimiter', ',') - - for index, feature in enumerate(features): - feature_colors = [] - if property_map: - if 'colors' in property_map: - map_value = property_map['colors'] - property_value = feature['properties'].get(map_value) - if property_value: - feature_colors += str(property_value).split(color_delimiter) - - if type(palette) == dict: - feature_colors = [palette[c] for c in feature_colors if c in palette] - elif type(palette) == list: - feature_colors.append(palette[index % len(palette)]) - - if outline: - feature_colors.append(outline) - - feature_colors = [name_to_hex(c) for c in feature_colors] - feature['properties']['colors'] = ','.join(feature_colors) - return features diff --git a/uvdat/core/views.py b/uvdat/core/views.py deleted file mode 100644 index caeda1c2..00000000 --- a/uvdat/core/views.py +++ /dev/null @@ -1,267 +0,0 @@ -import json -from pathlib import Path -import tempfile - -from django.http import HttpResponse -from django_large_image.rest import LargeImageFileDetailMixin -from drf_yasg.utils import swagger_auto_schema -import ijson -import large_image -from rest_framework import status -from rest_framework.decorators import action -from rest_framework.response import Response -from rest_framework.viewsets import GenericViewSet, ModelViewSet, mixins - -from uvdat.core.models import Chart, City, Dataset, DerivedRegion, Region, SimulationResult -from uvdat.core.serializers import ( - ChartSerializer, - CitySerializer, - DatasetSerializer, - DerivedRegionCreationSerializer, - DerivedRegionDetailSerializer, - DerivedRegionListSerializer, - NetworkNodeSerializer, - RegionFeatureCollectionSerializer, - SimulationResultSerializer, -) -from uvdat.core.tasks.charts import add_gcc_chart_datum -from uvdat.core.tasks.conversion import convert_raw_data -from uvdat.core.tasks.networks import network_gcc, construct_edge_list -from uvdat.core.tasks.regions import DerivedRegionCreationException, create_derived_region -from uvdat.core.tasks.simulations import get_available_simulations, run_simulation - - -class DerivedRegionViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet): - queryset = DerivedRegion.objects.all() - serializer_class = DerivedRegionListSerializer - - def get_serializer_class(self): - if self.detail: - return DerivedRegionDetailSerializer - - return super().get_serializer_class() - - @action(detail=True, methods=['GET']) - def as_feature(self, request, *args, **kwargs): - obj: DerivedRegion = self.get_object() - feature = { - "type": "Feature", - "geometry": json.loads(obj.boundary.geojson), - "properties": DerivedRegionListSerializer(instance=obj).data, - } - - return HttpResponse(json.dumps(feature)) - - @swagger_auto_schema(request_body=DerivedRegionCreationSerializer) - def create(self, request, *args, **kwargs): - serializer = DerivedRegionCreationSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - - try: - data = serializer.validated_data - derived_region = create_derived_region( - name=data['name'], - city_id=data['city'], - region_ids=data['regions'], - operation=data['operation'], - ) - except DerivedRegionCreationException as e: - return Response(str(e), status=400) - - return Response( - DerivedRegionDetailSerializer(instance=derived_region).data, - status=status.HTTP_201_CREATED, - ) - - -class CityViewSet(ModelViewSet): - queryset = City.objects.all() - serializer_class = CitySerializer - - -class DatasetViewSet(ModelViewSet, LargeImageFileDetailMixin): - serializer_class = DatasetSerializer - FILE_FIELD_NAME = 'raster_file' - - def get_queryset(self): - city_id = self.request.query_params.get('city') - if city_id: - return Dataset.objects.filter(city__id=city_id) - else: - return Dataset.objects.all() - - @action(detail=True, methods=['get']) - def regions(self, request, **kwargs): - dataset = self.get_object() - if dataset.category != 'region': - return HttpResponse('Not a region dataset', status=400) - - # Serialize all regions as a feature collection - multipolygons = Region.objects.filter(dataset=dataset) - serializer = RegionFeatureCollectionSerializer() - return HttpResponse(serializer.serialize(multipolygons, geometry_field='boundary')) - - @action( - detail=True, - methods=['get'], - url_path=r'vector-tiles/(?P\d+)/(?P\d+)/(?P\d+)', - url_name='vector_tiles', - ) - def get_vector_tile(self, request, x: int, y: int, z: int, **kwargs): - dataset = self.get_object() - with dataset.vector_tiles_file.open() as vector_tile_json: - # use ijson to fetch only needed key (much faster than json parse) - tile = ijson.items(vector_tile_json, f'{z}.{x}.{y}', use_float=True) - try: - return HttpResponse(json.dumps(tile.__next__()), status=200) - except StopIteration: - return HttpResponse(status=404) - - @action( - detail=True, - methods=['get'], - url_path=r'raster-data/(?P[\d*\.?\d*]+)', - url_name='raster_data', - ) - def get_raster_data(self, request, resolution: str = '1', **kwargs): - dataset = self.get_object() - if dataset.raster_file: - with tempfile.TemporaryDirectory() as tmp: - raster_path = Path(tmp, 'raster') - with open(raster_path, 'wb') as raster_file: - raster_file.write(dataset.raster_file.read()) - source = large_image.open(raster_path) - data, data_format = source.getRegion(format='numpy') - data = data[:, :, 0] - if resolution: - resolution = float(resolution) - if resolution != 1.0: - step = int(1 / resolution) - data = data[::step][::step] - return HttpResponse(json.dumps(data.tolist()), status=200) - else: - return HttpResponse('Dataset has no raster file.', status=400) - - @action( - detail=True, - methods=['get'], - url_path=r'network', - url_name='network', - ) - def get_network_nodes(self, request, **kwargs): - dataset = self.get_object() - return Response( - [NetworkNodeSerializer(n).data for n in dataset.network_nodes.all()], status=200 - ) - - @action( - detail=True, - methods=['get'], - url_path=r'convert', - url_name='convert', - ) - def spawn_conversion_task(self, request, **kwargs): - dataset = self.get_object() - dataset.geodata_file = None - dataset.vector_tiles_file = None - dataset.raster_file = None - dataset.processing = True - dataset.save() - convert_raw_data.delay(dataset.id) - return Response(status=200) - - @action( - detail=True, - methods=['get'], - url_path=r'gcc', - url_name='gcc', - ) - def get_gcc(self, request, **kwargs): - dataset = self.get_object() - if not dataset.network: - return Response('This dataset is not a network dataset.', status=400) - if "exclude_nodes" not in dict(request.query_params): - return Response('Please specify a list of nodes to exclude in `exclude_nodes`.') - exclude_nodes = request.query_params.get('exclude_nodes') - exclude_nodes = exclude_nodes.split(',') - exclude_nodes = [int(n) for n in exclude_nodes if len(n)] - - excluded_node_names = [] - for node in dataset.network_nodes.all(): - if node.id in exclude_nodes: - excluded_node_names.append(node.name) - - edge_list = construct_edge_list(dataset) - gcc = network_gcc(edge_list, exclude_nodes) - add_gcc_chart_datum(dataset, excluded_node_names, len(gcc)) - return Response(gcc, status=200) - - -class ChartViewSet(GenericViewSet, mixins.ListModelMixin): - queryset = Chart.objects.all() - serializer_class = ChartSerializer - - def get_queryset(self, **kwargs): - city_id = kwargs.get('city') - if city_id: - return Chart.objects.filter(city__id=city_id) - return Chart.objects.all() - - @action(detail=True, methods=['post']) - def clear(self, request, **kwargs): - chart = self.get_object() - if not chart.clearable: - return HttpResponse('Not a clearable chart.', status=400) - - chart.metadata = [] - chart.chart_data = {} - chart.save() - return HttpResponse(status=200) - - -class SimulationViewSet(GenericViewSet): - # Not based on a database model; - # Available Simulations must be hard-coded - # and associated with a function - - @action( - detail=False, - methods=['get'], - url_path=r'available/city/(?P[\d*]+)', - ) - def list_available(self, request, city_id: int, **kwargs): - sims = get_available_simulations(city_id) - return HttpResponse( - json.dumps(sims), - status=200, - ) - - @action( - detail=False, - methods=['get'], - url_path=r'(?P[\d*]+)/city/(?P[\d*]+)/results', - ) - def list_results(self, request, simulation_id: int, city_id: int, **kwargs): - return HttpResponse( - json.dumps( - list( - SimulationResultSerializer(s).data - for s in SimulationResult.objects.filter( - simulation_id=int(simulation_id), city__id=city_id - ).all() - ) - ), - status=200, - ) - - @action( - detail=False, - methods=['post'], - url_path=r'run/(?P[\d*]+)/city/(?P[\d*]+)', - ) - def run(self, request, simulation_id: int, city_id: int, **kwargs): - result = run_simulation(int(simulation_id), int(city_id), **request.data) - return HttpResponse( - json.dumps({'result': result}), - status=200, - ) diff --git a/uvdat/urls.py b/uvdat/urls.py index 0eaf1d12..5be1d17c 100644 --- a/uvdat/urls.py +++ b/uvdat/urls.py @@ -5,7 +5,16 @@ from drf_yasg.views import get_schema_view from rest_framework import permissions, routers -from uvdat.core import views +from uvdat.core.rest import ( + ChartViewSet, + ContextViewSet, + DatasetViewSet, + DerivedRegionViewSet, + RasterMapLayerViewSet, + SimulationViewSet, + SourceRegionViewSet, + VectorMapLayerViewSet, +) router = routers.SimpleRouter() # OpenAPI generation @@ -15,11 +24,14 @@ permission_classes=(permissions.AllowAny,), ) -router.register(r'datasets', views.DatasetViewSet, basename='datasets') -router.register(r'cities', views.CityViewSet, basename='cities') -router.register(r'charts', views.ChartViewSet, basename='charts') -router.register(r'simulations', views.SimulationViewSet, basename='simulations') -router.register(r'derived_regions', views.DerivedRegionViewSet, basename='derived_regions') +router.register(r'contexts', ContextViewSet, basename='contexts') +router.register(r'datasets', DatasetViewSet, basename='datasets') +router.register(r'charts', ChartViewSet, basename='charts') +router.register(r'rasters', RasterMapLayerViewSet, basename='rasters') +router.register(r'vectors', VectorMapLayerViewSet, basename='vectors') +router.register(r'source-regions', SourceRegionViewSet, basename='source-regions') +router.register(r'derived-regions', DerivedRegionViewSet, basename='derived-regions') +router.register(r'simulations', SimulationViewSet, basename='simulations') urlpatterns = [ path('accounts/', include('allauth.urls')), diff --git a/web/package-lock.json b/web/package-lock.json index 5458da78..ad6bc3b0 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -24,21 +24,31 @@ "vuetify": "^3.3.16" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.4.0", - "@typescript-eslint/parser": "^5.4.0", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.62.0", + "@typescript-eslint/typescript-estree": "^6.9.0", "@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-plugin-typescript": "~5.0.0", "@vue/cli-service": "~5.0.0", "@vue/eslint-config-typescript": "^9.1.0", - "eslint": "^7.32.0", + "eslint": "^8.52.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-vue": "^8.0.3", "prettier": "^2.4.1", "sass": "^1.32.7", "sass-loader": "^12.0.0", - "typescript": "^5.0.0" + "typescript": "5.1.5" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/@achrinza/node-ipc": { @@ -69,12 +79,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -120,12 +131,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", - "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.21.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -235,34 +246,34 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", - "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -392,30 +403,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", - "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -460,13 +471,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -474,9 +485,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", - "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1670,33 +1681,33 @@ } }, "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", - "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.5", - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.5", - "@babel/types": "^7.21.5", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1705,13 +1716,13 @@ } }, "node_modules/@babel/types": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", - "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.21.5", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1743,73 +1754,41 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "dependencies": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -1821,15 +1800,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -1842,6 +1812,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/js": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", + "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@girder/oauth-client": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@girder/oauth-client/-/oauth-client-0.8.0.tgz", @@ -1866,23 +1845,36 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@jridgewell/gen-mapping": { @@ -2373,9 +2365,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", + "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", "dev": true }, "node_modules/@types/mapbox__point-geometry": { @@ -2446,12 +2438,6 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, - "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, "node_modules/@types/send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", @@ -2511,20 +2497,19 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.2.tgz", - "integrity": "sha512-yVrXupeHjRxLDcPKL10sGQ/QlVrA8J5IYOEWVqk0lJaSZP7X5DfnP7Ns3cc74/blmbipQ1htFNVGsHX6wsYm0A==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.9.0.tgz", + "integrity": "sha512-qT4lr2jysDQBQOPsCCvpPUZHjbABoTJW8V9ZzIYKHMfppJtpdtzszDYsldwhFxlhvrp7aCHeXD1Lb9M1zhwWwQ==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/type-utils": "5.59.2", - "@typescript-eslint/utils": "5.59.2", - "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", + "@typescript-eslint/experimental-utils": "5.9.0", + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/type-utils": "5.9.0", + "debug": "^4.3.2", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.2.0", + "semver": "^7.3.5", "tsutils": "^3.21.0" }, "engines": { @@ -2557,9 +2542,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -2577,16 +2562,18 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/@typescript-eslint/parser": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.2.tgz", - "integrity": "sha512-uq0sKyw6ao1iFOZZGk9F8Nro/8+gfB5ezl1cA06SrqbgJAt0SRoFhb9pXaHvkrxUpZaoLxt8KlovHNk8Gp6/HQ==", + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.9.0.tgz", + "integrity": "sha512-ZnLVjBrf26dn7ElyaSKa6uDhqwvAi4jBBmHK1VxuFGPRAxhdi18ubQYSGA7SRiFiES3q9JiBOBHEBStOFkwD2g==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/typescript-estree": "5.59.2", - "debug": "^4.3.4" + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/typescript-estree": "5.9.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2597,6 +2584,28 @@ }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.9.0.tgz", + "integrity": "sha512-kxo3xL2mB7XmiVZcECbaDwYCt3qFXz99tBSuVJR4L/sR7CJ+UNAPrYILILktGj1ppfZ/jNt/cWYbziJUlHl1Pw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/visitor-keys": "5.9.0", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependenciesMeta": { "typescript": { @@ -2604,33 +2613,49 @@ } } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz", - "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/visitor-keys": "5.59.2" + "yallist": "^4.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.2.tgz", - "integrity": "sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.2", - "@typescript-eslint/utils": "5.59.2", - "debug": "^4.3.4", - "tsutils": "^3.21.0" + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2640,7 +2665,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -2648,11 +2673,15 @@ } } }, - "node_modules/@typescript-eslint/types": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz", - "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==", + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2661,14 +2690,27 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz", - "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==", + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/visitor-keys": "5.59.2", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2688,7 +2730,24 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", @@ -2700,10 +2759,10 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "node_modules/@typescript-eslint/parser/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -2715,26 +2774,38 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "node_modules/@typescript-eslint/parser/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/@typescript-eslint/utils": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz", - "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.9.0.tgz", + "integrity": "sha512-DKtdIL49Qxk2a8icF6whRk7uThuVz4A6TCXfjdJSwOsf+9ree7vgQWcx0KOyCdk0i9ETX666p4aMhrRhxhUkyg==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/typescript-estree": "5.59.2", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/visitor-keys": "5.9.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.9.0.tgz", + "integrity": "sha512-uVCb9dJXpBrK1071ri5aEW7ZHdDHAiqEjYznF3HSSvAJXyrkxGOw2Ejibz/q6BXdT8lea8CMI0CzKNFTNI6TEQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "5.9.0", + "debug": "^4.3.2", + "tsutils": "^3.21.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2744,10 +2815,85 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { + "node_modules/@typescript-eslint/types": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.9.0.tgz", + "integrity": "sha512-mWp6/b56Umo1rwyGCk8fPIzb9Migo8YOniBGPAQDNC6C52SeyNGN4gsVwQTAR+RS2L5xyajON4hOLwAGwPtUwg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.0.tgz", + "integrity": "sha512-NJM2BnJFZBEAbCfBP00zONKXvMqihZCrmwCaik0UhLr0vAgb6oguXxLX1k00oQyD+vZZ+CJn3kocvv2yxm4awQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.9.0", + "@typescript-eslint/visitor-keys": "6.9.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.0.tgz", + "integrity": "sha512-+KB0lbkpxBkBSiVCuQvduqMJy+I1FyDbdwSpM3IoBS7APl4Bu15lStPjgBIdykdRqQNYqYNMa8Kuidax6phaEw==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.0.tgz", + "integrity": "sha512-dGtAfqjV6RFOtIP8I0B4ZTBRrlTT8NHHlZZSchQx3qReaoDeXhYM++M4So2AgFK9ZB0emRPA6JI1HkafzA2Ibg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.9.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", @@ -2759,10 +2905,10 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -2774,20 +2920,20 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz", - "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.9.0.tgz", + "integrity": "sha512-6zq0mb7LV0ThExKlecvpfepiB+XEtFv/bzx7/jKSgyXTFD7qjmSu1FoiS0x3OZaiS+UIXpH2vd9O89f02RCtgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.2", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "5.9.0", + "eslint-visitor-keys": "^3.0.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2797,6 +2943,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@vue/babel-helper-vue-jsx-merge-props": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz", @@ -2902,9 +3054,9 @@ } }, "node_modules/@vue/babel-preset-app/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3336,9 +3488,9 @@ } }, "node_modules/@vue/cli-shared-utils/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3554,9 +3706,9 @@ }, "node_modules/@vue/vue-loader-v15": { "name": "vue-loader", - "version": "15.10.1", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.1.tgz", - "integrity": "sha512-SaPHK1A01VrNthlix6h1hq4uJu7S/z0kdLUb6klubo738NeQoLbS6V9/d8Pv19tU0XdQKju3D1HSKuI8wJ5wMA==", + "version": "15.11.1", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.11.1.tgz", + "integrity": "sha512-0iw4VchYLePqJfJu9s62ACWUXeSqM30SQqlIftbYWM3C+jpPcEHKSPUZBLjSF9au4HTHQ/naF6OGnO3Q/qGR3Q==", "dev": true, "dependencies": { "@vue/component-compiler-utils": "^3.1.0", @@ -3564,6 +3716,21 @@ "loader-utils": "^1.1.0", "vue-hot-reload-api": "^2.3.0", "vue-style-loader": "^4.1.0" + }, + "peerDependencies": { + "css-loader": "*", + "webpack": "^3.0.0 || ^4.1.0 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "cache-loader": { + "optional": true + }, + "prettier": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } } }, "node_modules/@vue/vue-loader-v15/node_modules/hash-sum": { @@ -3780,9 +3947,9 @@ } }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -3930,15 +4097,6 @@ "node": ">=0.4.2" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-escapes": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", @@ -4061,13 +4219,10 @@ } }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/array-flatten": { "version": "2.1.2", @@ -4105,15 +4260,6 @@ "node": ">=0.8" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -4668,9 +4814,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001482", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz", - "integrity": "sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ==", + "version": "1.0.30001554", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001554.tgz", + "integrity": "sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ==", "dev": true, "funding": [ { @@ -4751,9 +4897,9 @@ } }, "node_modules/chart.js": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.3.tgz", - "integrity": "sha512-aTk7pBw+x6sQYhon/NR3ikfUJuym/LdgpTlgZRe2PaEhjUMKBKyNaFCMVRAyTEWYFNO7qRu7iQVqOw/OqzxZxQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz", + "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==", "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" @@ -5550,9 +5696,9 @@ } }, "node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -5621,9 +5767,9 @@ } }, "node_modules/css-loader/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -6514,18 +6660,6 @@ "node": ">=10.13.0" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -6582,57 +6716,55 @@ } }, "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", + "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", "dev": true, "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.52.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6691,33 +6823,6 @@ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/eslint-plugin-vue/node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-plugin-vue/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-plugin-vue/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -6731,9 +6836,9 @@ } }, "node_modules/eslint-plugin-vue/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -6765,33 +6870,36 @@ } }, "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" }, "engines": { - "node": ">=6" + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" } }, "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, "engines": { - "node": ">=4" + "node": ">=10" } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6915,27 +7023,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/eslint/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -7011,36 +7098,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" } }, - "node_modules/eslint/node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, "engines": { - "node": ">=4" + "node": ">=10.13.0" } }, "node_modules/eslint/node_modules/globals": { @@ -7067,25 +7175,49 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, "engines": { - "node": ">= 4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "dependencies": { - "yallist": "^4.0.0" + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint/node_modules/path-key": { @@ -7097,21 +7229,6 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint/node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7172,40 +7289,21 @@ "node": ">= 8" } }, - "node_modules/eslint/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { @@ -7738,9 +7836,9 @@ } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -8181,10 +8279,10 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "node_modules/gzip-size": { @@ -8866,6 +8964,15 @@ "node": ">=0.12.0" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -9025,13 +9132,12 @@ "dev": true }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -9430,12 +9536,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -10093,12 +10193,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -10230,9 +10324,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -10565,17 +10659,17 @@ "integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==" }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -10976,9 +11070,9 @@ } }, "node_modules/postcss": { - "version": "8.4.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", - "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -11148,9 +11242,9 @@ } }, "node_modules/postcss-loader/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -11612,15 +11706,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/progress-webpack-plugin": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/progress-webpack-plugin/-/progress-webpack-plugin-1.0.16.tgz", @@ -12437,9 +12522,9 @@ } }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -12709,56 +12794,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/sntp": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", @@ -13001,12 +13036,6 @@ "wbuf": "^1.7.3" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, "node_modules/sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -13233,44 +13262,6 @@ "node": ">= 10" } }, - "node_modules/table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/taffydb": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", @@ -13598,6 +13589,18 @@ "node": "*" } }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-loader": { "version": "9.4.2", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", @@ -13688,9 +13691,9 @@ } }, "node_modules/ts-loader/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -13798,9 +13801,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.5.tgz", + "integrity": "sha512-FOH+WN/DQjUvN6WgW+c4Ml3yi0PH+a/8q+kNIfRehv1wLhWONedw85iu+vQ39Wp49IzTJEsZ2lyLXpBF7mkF1g==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -14032,12 +14035,6 @@ "node": ">=4" } }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -14181,9 +14178,9 @@ } }, "node_modules/vue-eslint-parser/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -14951,15 +14948,6 @@ "resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.3.2.tgz", "integrity": "sha512-A26BOOo7sHAagyxG7iuRhnKMO7Q3mEOiOT4oGUmohtN/Li5wameeU4S6f8vWw6NADTVKljBs8bzA8JPQgSEMVQ==" }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", @@ -15117,6 +15105,18 @@ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==" }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yorkie": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yorkie/-/yorkie-2.0.0.tgz", @@ -15198,6 +15198,12 @@ } }, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, "@achrinza/node-ipc": { "version": "9.2.6", "resolved": "https://registry.npmjs.org/@achrinza/node-ipc/-/node-ipc-9.2.6.tgz", @@ -15220,12 +15226,13 @@ } }, "@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" } }, "@babel/compat-data": { @@ -15258,12 +15265,12 @@ } }, "@babel/generator": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", - "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.21.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -15343,28 +15350,28 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", - "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -15461,24 +15468,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", - "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -15511,20 +15518,20 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", - "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==" + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.18.6", @@ -16322,42 +16329,42 @@ } }, "@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", - "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.5", - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.5", - "@babel/types": "^7.21.5", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", - "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.21.5", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -16377,66 +16384,37 @@ } }, "@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true }, "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "requires": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - } - }, "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -16445,6 +16423,12 @@ } } }, + "@eslint/js": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", + "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "dev": true + }, "@girder/oauth-client": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@girder/oauth-client/-/oauth-client-0.8.0.tgz", @@ -16469,20 +16453,26 @@ } }, "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.0", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" } }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "@jridgewell/gen-mapping": { @@ -16912,9 +16902,9 @@ } }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", + "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", "dev": true }, "@types/mapbox__point-geometry": { @@ -16985,12 +16975,6 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, - "@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, "@types/send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", @@ -17050,20 +17034,19 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.2.tgz", - "integrity": "sha512-yVrXupeHjRxLDcPKL10sGQ/QlVrA8J5IYOEWVqk0lJaSZP7X5DfnP7Ns3cc74/blmbipQ1htFNVGsHX6wsYm0A==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.9.0.tgz", + "integrity": "sha512-qT4lr2jysDQBQOPsCCvpPUZHjbABoTJW8V9ZzIYKHMfppJtpdtzszDYsldwhFxlhvrp7aCHeXD1Lb9M1zhwWwQ==", "dev": true, "requires": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/type-utils": "5.59.2", - "@typescript-eslint/utils": "5.59.2", - "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", + "@typescript-eslint/experimental-utils": "5.9.0", + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/type-utils": "5.9.0", + "debug": "^4.3.2", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.2.0", + "semver": "^7.3.5", "tsutils": "^3.21.0" }, "dependencies": { @@ -17077,9 +17060,143 @@ } }, "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.9.0.tgz", + "integrity": "sha512-ZnLVjBrf26dn7ElyaSKa6uDhqwvAi4jBBmHK1VxuFGPRAxhdi18ubQYSGA7SRiFiES3q9JiBOBHEBStOFkwD2g==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/typescript-estree": "5.9.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "dependencies": { + "@typescript-eslint/typescript-estree": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.9.0.tgz", + "integrity": "sha512-kxo3xL2mB7XmiVZcECbaDwYCt3qFXz99tBSuVJR4L/sR7CJ+UNAPrYILILktGj1ppfZ/jNt/cWYbziJUlHl1Pw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/visitor-keys": "5.9.0", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + } + }, + "@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -17093,103 +17210,64 @@ } } }, - "@typescript-eslint/parser": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.2.tgz", - "integrity": "sha512-uq0sKyw6ao1iFOZZGk9F8Nro/8+gfB5ezl1cA06SrqbgJAt0SRoFhb9pXaHvkrxUpZaoLxt8KlovHNk8Gp6/HQ==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/typescript-estree": "5.59.2", - "debug": "^4.3.4" - } - }, "@typescript-eslint/scope-manager": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz", - "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.9.0.tgz", + "integrity": "sha512-DKtdIL49Qxk2a8icF6whRk7uThuVz4A6TCXfjdJSwOsf+9ree7vgQWcx0KOyCdk0i9ETX666p4aMhrRhxhUkyg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/visitor-keys": "5.59.2" + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/visitor-keys": "5.9.0" } }, "@typescript-eslint/type-utils": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.2.tgz", - "integrity": "sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.9.0.tgz", + "integrity": "sha512-uVCb9dJXpBrK1071ri5aEW7ZHdDHAiqEjYznF3HSSvAJXyrkxGOw2Ejibz/q6BXdT8lea8CMI0CzKNFTNI6TEQ==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.59.2", - "@typescript-eslint/utils": "5.59.2", - "debug": "^4.3.4", + "@typescript-eslint/experimental-utils": "5.9.0", + "debug": "^4.3.2", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz", - "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.9.0.tgz", + "integrity": "sha512-mWp6/b56Umo1rwyGCk8fPIzb9Migo8YOniBGPAQDNC6C52SeyNGN4gsVwQTAR+RS2L5xyajON4hOLwAGwPtUwg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz", - "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.0.tgz", + "integrity": "sha512-NJM2BnJFZBEAbCfBP00zONKXvMqihZCrmwCaik0UhLr0vAgb6oguXxLX1k00oQyD+vZZ+CJn3kocvv2yxm4awQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/visitor-keys": "5.59.2", + "@typescript-eslint/types": "6.9.0", + "@typescript-eslint/visitor-keys": "6.9.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } + "@typescript-eslint/types": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.0.tgz", + "integrity": "sha512-+KB0lbkpxBkBSiVCuQvduqMJy+I1FyDbdwSpM3IoBS7APl4Bu15lStPjgBIdykdRqQNYqYNMa8Kuidax6phaEw==", + "dev": true }, - "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "@typescript-eslint/visitor-keys": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.0.tgz", + "integrity": "sha512-dGtAfqjV6RFOtIP8I0B4ZTBRrlTT8NHHlZZSchQx3qReaoDeXhYM++M4So2AgFK9ZB0emRPA6JI1HkafzA2Ibg==", "dev": true, "requires": { - "lru-cache": "^6.0.0" + "@typescript-eslint/types": "6.9.0", + "eslint-visitor-keys": "^3.4.1" } }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/utils": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz", - "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/typescript-estree": "5.59.2", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "dependencies": { "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -17200,9 +17278,9 @@ } }, "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -17217,15 +17295,21 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz", - "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.9.0.tgz", + "integrity": "sha512-6zq0mb7LV0ThExKlecvpfepiB+XEtFv/bzx7/jKSgyXTFD7qjmSu1FoiS0x3OZaiS+UIXpH2vd9O89f02RCtgw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.59.2", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "5.9.0", + "eslint-visitor-keys": "^3.0.0" } }, + "@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "@vue/babel-helper-vue-jsx-merge-props": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz", @@ -17311,9 +17395,9 @@ } }, "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -17625,9 +17709,9 @@ } }, "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -17816,9 +17900,9 @@ "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==" }, "@vue/vue-loader-v15": { - "version": "npm:vue-loader@15.10.1", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.1.tgz", - "integrity": "sha512-SaPHK1A01VrNthlix6h1hq4uJu7S/z0kdLUb6klubo738NeQoLbS6V9/d8Pv19tU0XdQKju3D1HSKuI8wJ5wMA==", + "version": "npm:vue-loader@15.11.1", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.11.1.tgz", + "integrity": "sha512-0iw4VchYLePqJfJu9s62ACWUXeSqM30SQqlIftbYWM3C+jpPcEHKSPUZBLjSF9au4HTHQ/naF6OGnO3Q/qGR3Q==", "dev": true, "requires": { "@vue/component-compiler-utils": "^3.1.0", @@ -18035,9 +18119,9 @@ } }, "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true }, "acorn-import-assertions": { @@ -18146,12 +18230,6 @@ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==" }, - "ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true - }, "ansi-escapes": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", @@ -18244,13 +18322,10 @@ } }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "array-flatten": { "version": "2.1.2", @@ -18282,12 +18357,6 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", "integrity": "sha512-u1L0ZLywRziOVjUhRxI0Qg9G+4RnFB9H/Rq40YWn0dieDgO7vAYeJz6jKAO6t/aruzlDFLAPkQTT87e+f8Imaw==" }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, "async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -18685,9 +18754,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001482", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz", - "integrity": "sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ==", + "version": "1.0.30001554", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001554.tgz", + "integrity": "sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ==", "dev": true }, "case-sensitive-paths-webpack-plugin": { @@ -18739,9 +18808,9 @@ } }, "chart.js": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.3.tgz", - "integrity": "sha512-aTk7pBw+x6sQYhon/NR3ikfUJuym/LdgpTlgZRe2PaEhjUMKBKyNaFCMVRAyTEWYFNO7qRu7iQVqOw/OqzxZxQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz", + "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==", "peer": true, "requires": { "@kurkle/color": "^0.3.0" @@ -19365,9 +19434,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -19413,9 +19482,9 @@ } }, "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -20097,15 +20166,6 @@ "tapable": "^2.2.0" } }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, "entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -20153,68 +20213,51 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", + "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", "dev": true, "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.52.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -20266,29 +20309,39 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" } }, "globals": { @@ -20306,19 +20359,31 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "lru-cache": { + "locate-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { - "yallist": "^4.0.0" + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" } }, "path-key": { @@ -20327,15 +20392,6 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -20374,12 +20430,6 @@ "requires": { "isexe": "^2.0.0" } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true } } }, @@ -20413,21 +20463,6 @@ "vue-eslint-parser": "^8.0.1" }, "dependencies": { - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -20438,9 +20473,9 @@ } }, "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -20465,26 +20500,26 @@ } }, "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true } } }, "eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, "eslint-webpack-plugin": { @@ -20568,22 +20603,16 @@ } }, "espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "requires": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^3.4.1" } }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, "esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -20993,9 +21022,9 @@ } }, "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -21345,10 +21374,10 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "gzip-size": { @@ -21858,6 +21887,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -21980,13 +22015,12 @@ "dev": true }, "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "js2xmlparser": { @@ -22326,12 +22360,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -22842,12 +22870,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -22948,9 +22970,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -23214,17 +23236,17 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" } }, "ora": { @@ -23541,9 +23563,9 @@ } }, "postcss": { - "version": "8.4.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", - "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "requires": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -23644,9 +23666,9 @@ } }, "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -23944,12 +23966,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, "progress-webpack-plugin": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/progress-webpack-plugin/-/progress-webpack-plugin-1.0.16.tgz", @@ -24543,9 +24559,9 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, "send": { @@ -24781,43 +24797,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, "sntp": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", @@ -25043,12 +25022,6 @@ "wbuf": "^1.7.3" } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, "sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -25216,39 +25189,6 @@ } } }, - "table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, "taffydb": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", @@ -25491,6 +25431,13 @@ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==" }, + "ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "requires": {} + }, "ts-loader": { "version": "9.4.2", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", @@ -25553,9 +25500,9 @@ } }, "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -25640,9 +25587,9 @@ } }, "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.5.tgz", + "integrity": "sha512-FOH+WN/DQjUvN6WgW+c4Ml3yi0PH+a/8q+kNIfRehv1wLhWONedw85iu+vQ39Wp49IzTJEsZ2lyLXpBF7mkF1g==", "devOptional": true }, "uglify-js": { @@ -25803,12 +25750,6 @@ "integrity": "sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==", "optional": true }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -25921,9 +25862,9 @@ } }, "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -26483,12 +26424,6 @@ "resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.3.2.tgz", "integrity": "sha512-A26BOOo7sHAagyxG7iuRhnKMO7Q3mEOiOT4oGUmohtN/Li5wameeU4S6f8vWw6NADTVKljBs8bzA8JPQgSEMVQ==" }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", @@ -26601,6 +26536,12 @@ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==" }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + }, "yorkie": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yorkie/-/yorkie-2.0.0.tgz", diff --git a/web/package.json b/web/package.json index c57694c7..08626223 100644 --- a/web/package.json +++ b/web/package.json @@ -25,21 +25,22 @@ "vuetify": "^3.3.16" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.4.0", - "@typescript-eslint/parser": "^5.4.0", + "@typescript-eslint/typescript-estree": "^6.9.0", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.62.0", + "@vue/cli-service": "~5.0.0", "@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-plugin-typescript": "~5.0.0", - "@vue/cli-service": "~5.0.0", "@vue/eslint-config-typescript": "^9.1.0", - "eslint": "^7.32.0", + "eslint": "^8.52.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-vue": "^8.0.3", "prettier": "^2.4.1", "sass": "^1.32.7", "sass-loader": "^12.0.0", - "typescript": "^5.0.0" + "typescript": "5.1.5" }, "eslintConfig": { "root": true, diff --git a/web/src/App.vue b/web/src/App.vue index f06ad8ec..de3f4fb6 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -2,16 +2,16 @@ import { defineComponent, ref, onMounted } from "vue"; import { currentError, - currentMapDataSource, - currentCity, - cities, + currentContext, + currentDataset, + availableContexts, loading, - loadCities, - activeChart, - activeSimulation, + currentChart, + currentSimulationType, showMapBaseLayer, } from "./store"; -import { updateVisibleLayers } from "@/layers"; +import { loadContexts } from "./storeFunctions"; +import { updateBaseLayer } from "@/layers"; import OpenLayersMap from "./components/map/OpenLayersMap.vue"; import MainDrawerContents from "./components/MainDrawerContents.vue"; import OptionsDrawerContents from "./components/OptionsDrawerContents.vue"; @@ -29,20 +29,19 @@ export default defineComponent({ setup() { const drawer = ref(true); - onMounted(loadCities); - currentMapDataSource.value = undefined; + onMounted(loadContexts); return { drawer, - currentCity, - currentMapDataSource, - cities, + currentContext, + currentDataset, + availableContexts, loading, currentError, - activeChart, - activeSimulation, + currentChart, + currentSimulationType, showMapBaseLayer, - updateVisibleLayers, + updateBaseLayer, }; }, }); @@ -63,10 +62,11 @@ export default defineComponent({ {{ currentError }} - + @@ -103,15 +103,15 @@ export default defineComponent({
- - + +
@@ -127,12 +127,12 @@ export default defineComponent({ left: 250px; } .shifted-1 { - left: 250px; - width: calc(100% - 250px); + left: 300px; + width: calc(100% - 300px); } .shifted-2 { - left: 250px; - right: 250px; - width: calc(100% - 500px); + left: 300px; + right: 300px; + width: calc(100% - 600px); } diff --git a/web/src/api/rest.ts b/web/src/api/rest.ts index f55165c8..7e207a7a 100644 --- a/web/src/api/rest.ts +++ b/web/src/api/rest.ts @@ -1,35 +1,54 @@ import { apiClient } from "./auth"; import { - City, + Context, Dataset, NetworkNode, RasterData, Chart, - Simulation, + SimulationType, + DerivedRegion, + VectorMapLayer, + RasterMapLayer, } from "@/types"; -export async function getCities(): Promise { - return (await apiClient.get("cities")).data.results; +export async function getContexts(): Promise { + return (await apiClient.get("contexts")).data.results; } -export async function getCityDatasets(cityId: number): Promise { - return (await apiClient.get(`datasets?city=${cityId}`)).data.results; +export async function getContextDatasets( + contextId: number +): Promise { + return (await apiClient.get(`datasets?context=${contextId}`)).data.results; } -export async function getCityCharts(cityId: number): Promise { - return (await apiClient.get(`charts?city=${cityId}`)).data.results; +export async function getContextCharts(contextId: number): Promise { + return (await apiClient.get(`charts?context=${contextId}`)).data.results; } -export async function getCitySimulations( - cityId: number -): Promise { - return (await apiClient.get(`simulations/available/city/${cityId}`)).data; +export async function getContextDerivedRegions( + contextId: number +): Promise { + return (await apiClient.get(`derived-regions?context=${contextId}`)).data + .results; +} + +export async function getContextSimulationTypes( + contextId: number +): Promise { + return (await apiClient.get(`simulations/available/context/${contextId}`)) + .data; } export async function getDataset(datasetId: number): Promise { return (await apiClient.get(`datasets/${datasetId}`)).data; } +export async function getDatasetMapLayers( + datasetId: number +): Promise<(VectorMapLayer | RasterMapLayer)[]> { + return (await apiClient.get(`datasets/${datasetId}/map_layers`)).data; +} + export async function convertDataset(datasetId: number): Promise { return (await apiClient.get(`datasets/${datasetId}/convert`)).data; } @@ -42,22 +61,30 @@ export async function getDatasetNetwork( export async function getNetworkGCC( datasetId: number, + contextId: number, exclude_nodes: number[] ): Promise { return ( await apiClient.get( - `datasets/${datasetId}/gcc?exclude_nodes=${exclude_nodes.toString()}` + `datasets/${datasetId}/gcc?context=${contextId}&exclude_nodes=${exclude_nodes.toString()}` ) ).data; } -export async function getRasterData(datasetId: number): Promise { +export async function getMapLayer( + mapLayerId: number, + mapLayerType: string +): Promise { + return (await apiClient.get(`${mapLayerType}s/${mapLayerId}`)).data; +} + +export async function getRasterData(layerId: number): Promise { const resolution = 0.1; const data = ( - await apiClient.get(`datasets/${datasetId}/raster-data/${resolution}`) + await apiClient.get(`rasters/${layerId}/raster-data/${resolution}`) ).data; const { sourceBounds } = ( - await apiClient.get(`datasets/${datasetId}/info/metadata`) + await apiClient.get(`rasters/${layerId}/info/metadata`) ).data; return { data, @@ -71,12 +98,12 @@ export async function clearChart(chartId: number) { export async function runSimulation( simulationId: number, - cityId: number, + contextId: number, args: object ) { return ( await apiClient.post( - `simulations/run/${simulationId}/city/${cityId}/`, + `simulations/run/${simulationId}/context/${contextId}/`, args ) ).data; @@ -84,33 +111,31 @@ export async function runSimulation( export async function getSimulationResults( simulationId: number, - cityId: number + contextId: number ) { return ( - await apiClient.get(`simulations/${simulationId}/city/${cityId}/results/`) + await apiClient.get( + `simulations/${simulationId}/context/${contextId}/results/` + ) ).data; } -export async function listDerivedRegions() { - const res = await apiClient.get("derived_regions/"); - return res.data.results; -} - export async function getDerivedRegion(regionId: number) { - const res = await apiClient.get(`derived_regions/${regionId}/`); + const res = await apiClient.get(`derived-regions/${regionId}/`); return res.data; } export async function postDerivedRegion( name: string, - city: number, + context: number, regions: number[], - op: "union" | "intersection" + op: "union" | "intersection" | undefined ) { + if (!op) return; const operation = op.toUpperCase(); - const res = await apiClient.post("derived_regions/", { + const res = await apiClient.post("derived-regions/", { name, - city, + context, operation, regions, }); diff --git a/web/src/components/ChartJS.vue b/web/src/components/ChartJS.vue index feca54c9..6a68f845 100644 --- a/web/src/components/ChartJS.vue +++ b/web/src/components/ChartJS.vue @@ -1,6 +1,6 @@ -