goodmap 1.1.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- goodmap-1.1.12/LICENSE.md +21 -0
- goodmap-1.1.12/PKG-INFO +140 -0
- goodmap-1.1.12/README.md +103 -0
- goodmap-1.1.12/goodmap/__init__.py +1 -0
- goodmap-1.1.12/goodmap/clustering.py +75 -0
- goodmap-1.1.12/goodmap/config.py +42 -0
- goodmap-1.1.12/goodmap/core.py +46 -0
- goodmap-1.1.12/goodmap/core_api.py +500 -0
- goodmap-1.1.12/goodmap/data_models/location.py +68 -0
- goodmap-1.1.12/goodmap/data_validator.py +119 -0
- goodmap-1.1.12/goodmap/db.py +1466 -0
- goodmap-1.1.12/goodmap/exceptions.py +100 -0
- goodmap-1.1.12/goodmap/formatter.py +27 -0
- goodmap-1.1.12/goodmap/goodmap.py +100 -0
- goodmap-1.1.12/goodmap/templates/goodmap-admin.html +745 -0
- goodmap-1.1.12/goodmap/templates/map.html +129 -0
- goodmap-1.1.12/pyproject.toml +139 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 problematy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
goodmap-1.1.12/PKG-INFO
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: goodmap
|
|
3
|
+
Version: 1.1.12
|
|
4
|
+
Summary: Map engine to serve all the people :)
|
|
5
|
+
Author: Krzysztof Kolodzinski
|
|
6
|
+
Author-email: krzysztof.kolodzinski@problematy.pl
|
|
7
|
+
Requires-Python: >=3.10,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Provides-Extra: docs
|
|
14
|
+
Requires-Dist: Babel (>=2.10.3,<3.0.0)
|
|
15
|
+
Requires-Dist: Flask (==3.0.3)
|
|
16
|
+
Requires-Dist: Flask-Babel (>=4.0.0,<5.0.0)
|
|
17
|
+
Requires-Dist: Flask-WTF (>=1.2.1,<2.0.0)
|
|
18
|
+
Requires-Dist: PyYAML (>=6.0,<7.0)
|
|
19
|
+
Requires-Dist: aiohttp (>=3.8.4,<4.0.0)
|
|
20
|
+
Requires-Dist: deprecation (>=2.1.0,<3.0.0)
|
|
21
|
+
Requires-Dist: flask-restx (>=1.3.0,<2.0.0)
|
|
22
|
+
Requires-Dist: google-cloud-storage (>=2.7.0,<3.0.0)
|
|
23
|
+
Requires-Dist: gql (>=3.4.0,<4.0.0)
|
|
24
|
+
Requires-Dist: gunicorn (>=20.1.0,<21.0.0)
|
|
25
|
+
Requires-Dist: humanize (>=4.6.0,<5.0.0)
|
|
26
|
+
Requires-Dist: myst-parser (>=4.0.0,<5.0.0) ; extra == "docs"
|
|
27
|
+
Requires-Dist: numpy (>=2.2.0,<3.0.0)
|
|
28
|
+
Requires-Dist: platzky (>=1.0.0,<2.0.0)
|
|
29
|
+
Requires-Dist: pydantic (>=2.7.1,<3.0.0)
|
|
30
|
+
Requires-Dist: pysupercluster-problematy (>=0.7.8,<0.8.0)
|
|
31
|
+
Requires-Dist: scipy (>=1.15.1,<2.0.0)
|
|
32
|
+
Requires-Dist: sphinx (>=8.0.0,<9.0.0) ; extra == "docs"
|
|
33
|
+
Requires-Dist: sphinx-rtd-theme (>=3.0.0,<4.0.0) ; extra == "docs"
|
|
34
|
+
Requires-Dist: tomli (>=2.0.0,<3.0.0) ; extra == "docs"
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
[](https://coveralls.io/github/Problematy/goodmap)
|
|
39
|
+
|
|
40
|
+
# Good Map
|
|
41
|
+
|
|
42
|
+
Map engine to serve all the people ;)
|
|
43
|
+
|
|
44
|
+
## Setup
|
|
45
|
+
|
|
46
|
+
#### 0. Clone the repo
|
|
47
|
+
```
|
|
48
|
+
git clone --recursive
|
|
49
|
+
```
|
|
50
|
+
Remember, everytime you want to pull the newest changes, run:
|
|
51
|
+
```
|
|
52
|
+
git pull
|
|
53
|
+
git submodule update
|
|
54
|
+
```
|
|
55
|
+
because `goodmap` contains a submodule.
|
|
56
|
+
|
|
57
|
+
#TODO remove all submodule connected instructions after removing platzky submodule (see #157)
|
|
58
|
+
|
|
59
|
+
#### 1. Use python 3.10
|
|
60
|
+
If you have a different version of Python on your system, install python 3.10 alongside. For that, you can use [`pyenv`](https://github.com/pyenv/pyenv). Follow the [documentation](https://github.com/pyenv/pyenv?tab=readme-ov-file#installation). Useful commands: `pyenv help <command>`, `pyenv install`, `pyenv shell`, `pyenv versions`.
|
|
61
|
+
|
|
62
|
+
#### 2. Install `poetry` in Python 3.10
|
|
63
|
+
`poetry` can create virtual environments associated with a project. \
|
|
64
|
+
Make sure you are in the Python 3.10 environment and install:
|
|
65
|
+
```
|
|
66
|
+
pip install poetry
|
|
67
|
+
```
|
|
68
|
+
Useful commands: `poetry -h <command>`, `poetry env list`, `poetry env info`.
|
|
69
|
+
|
|
70
|
+
#### 3. Install dependencies
|
|
71
|
+
```
|
|
72
|
+
poetry install
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### 4. You're ready
|
|
76
|
+
|
|
77
|
+
When you enter the project directory, you can invoke any commands in your project like this:
|
|
78
|
+
```
|
|
79
|
+
poetry run <command>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Running App locally
|
|
83
|
+
|
|
84
|
+
### TL;DR
|
|
85
|
+
If you don't want to go through all the configuration, e.g. you just simply want to test if everything works,
|
|
86
|
+
you can simply run app with test dataset provided in `examples` directory:
|
|
87
|
+
|
|
88
|
+
> poetry run flask --app 'goodmap.goodmap:create_app(config_path="./examples/e2e_test_config.yml")' run
|
|
89
|
+
|
|
90
|
+
### Configuration
|
|
91
|
+
|
|
92
|
+
If you want to serve app with your configuration rename config-template.yml to config.yml and change its contents according to your needs.
|
|
93
|
+
|
|
94
|
+
Afterwards run it with:
|
|
95
|
+
> poetry run flask --app 'goodmap.goodmap:create_app(config_path="/PATH/TO/YOUR/CONFIG")' --debug run
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
| Option | Description |
|
|
99
|
+
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------|
|
|
100
|
+
| USE_LAZY_LOADING | Loads point data only after the user clicks a point. If set to false, point data is loaded together with the initial map. |
|
|
101
|
+
| FAKE_LOGIN | If set to true, allows access to the admin panel by simply selecting the role instead of logging in. **DO NOT USE IN PRODUCTION!** |
|
|
102
|
+
| SHOW_ACCESSIBILITY_TABLE | If set as true it shows special view to help with accessing application. |
|
|
103
|
+
|
|
104
|
+
## Database
|
|
105
|
+
|
|
106
|
+
The database is stored in JSON, in the `map` section. For an example database see `examples/e2e_test_data.json`. The first subsection `data` consists of the actual datapoints, representing points on a map.
|
|
107
|
+
|
|
108
|
+
Datapoints have fields. The next subsections define special types of fields:
|
|
109
|
+
- `obligatory_fields` - here are explicitely stated all the fields that the application assumes are presnt in all datapoints. E.g.
|
|
110
|
+
```
|
|
111
|
+
"position",
|
|
112
|
+
"name",
|
|
113
|
+
"accessible_by"
|
|
114
|
+
```
|
|
115
|
+
TODO: `obligatory_fields` is a new subsection, start using it in the actual application
|
|
116
|
+
- `categories` - fields that can somehow be used in the app, for example by which datapoints can be filtered. Every category has a specified list of allowed values. E.g.
|
|
117
|
+
```
|
|
118
|
+
"accessible_by": ["bikes", "cars", "pedestrians"]
|
|
119
|
+
```
|
|
120
|
+
- `visible_data` - when a datapoint will be rendered as a pin on a map, these fields will be shown in the box when clicking on a pin. E.g.
|
|
121
|
+
```
|
|
122
|
+
"name",
|
|
123
|
+
"type_of_place"
|
|
124
|
+
```
|
|
125
|
+
- `meta-data` - some special data like
|
|
126
|
+
```
|
|
127
|
+
"uuid"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
You can define the fields in all these subsections. Besides these types of fields, there is no restriction on the number of fields a datapoint can have.
|
|
131
|
+
|
|
132
|
+
## Examples
|
|
133
|
+
|
|
134
|
+
You can find examples of working configuration and database in `examples/` directory:
|
|
135
|
+
- `e2e_test_config.yml` - Basic configuration example
|
|
136
|
+
- `e2e_test_data.json` - Example database with sample location data
|
|
137
|
+
- `mongo_e2e_test_config.yml` - MongoDB configuration example
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
goodmap-1.1.12/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+

|
|
2
|
+
[](https://coveralls.io/github/Problematy/goodmap)
|
|
3
|
+
|
|
4
|
+
# Good Map
|
|
5
|
+
|
|
6
|
+
Map engine to serve all the people ;)
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
#### 0. Clone the repo
|
|
11
|
+
```
|
|
12
|
+
git clone --recursive
|
|
13
|
+
```
|
|
14
|
+
Remember, everytime you want to pull the newest changes, run:
|
|
15
|
+
```
|
|
16
|
+
git pull
|
|
17
|
+
git submodule update
|
|
18
|
+
```
|
|
19
|
+
because `goodmap` contains a submodule.
|
|
20
|
+
|
|
21
|
+
#TODO remove all submodule connected instructions after removing platzky submodule (see #157)
|
|
22
|
+
|
|
23
|
+
#### 1. Use python 3.10
|
|
24
|
+
If you have a different version of Python on your system, install python 3.10 alongside. For that, you can use [`pyenv`](https://github.com/pyenv/pyenv). Follow the [documentation](https://github.com/pyenv/pyenv?tab=readme-ov-file#installation). Useful commands: `pyenv help <command>`, `pyenv install`, `pyenv shell`, `pyenv versions`.
|
|
25
|
+
|
|
26
|
+
#### 2. Install `poetry` in Python 3.10
|
|
27
|
+
`poetry` can create virtual environments associated with a project. \
|
|
28
|
+
Make sure you are in the Python 3.10 environment and install:
|
|
29
|
+
```
|
|
30
|
+
pip install poetry
|
|
31
|
+
```
|
|
32
|
+
Useful commands: `poetry -h <command>`, `poetry env list`, `poetry env info`.
|
|
33
|
+
|
|
34
|
+
#### 3. Install dependencies
|
|
35
|
+
```
|
|
36
|
+
poetry install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### 4. You're ready
|
|
40
|
+
|
|
41
|
+
When you enter the project directory, you can invoke any commands in your project like this:
|
|
42
|
+
```
|
|
43
|
+
poetry run <command>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Running App locally
|
|
47
|
+
|
|
48
|
+
### TL;DR
|
|
49
|
+
If you don't want to go through all the configuration, e.g. you just simply want to test if everything works,
|
|
50
|
+
you can simply run app with test dataset provided in `examples` directory:
|
|
51
|
+
|
|
52
|
+
> poetry run flask --app 'goodmap.goodmap:create_app(config_path="./examples/e2e_test_config.yml")' run
|
|
53
|
+
|
|
54
|
+
### Configuration
|
|
55
|
+
|
|
56
|
+
If you want to serve app with your configuration rename config-template.yml to config.yml and change its contents according to your needs.
|
|
57
|
+
|
|
58
|
+
Afterwards run it with:
|
|
59
|
+
> poetry run flask --app 'goodmap.goodmap:create_app(config_path="/PATH/TO/YOUR/CONFIG")' --debug run
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
| Option | Description |
|
|
63
|
+
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------|
|
|
64
|
+
| USE_LAZY_LOADING | Loads point data only after the user clicks a point. If set to false, point data is loaded together with the initial map. |
|
|
65
|
+
| FAKE_LOGIN | If set to true, allows access to the admin panel by simply selecting the role instead of logging in. **DO NOT USE IN PRODUCTION!** |
|
|
66
|
+
| SHOW_ACCESSIBILITY_TABLE | If set as true it shows special view to help with accessing application. |
|
|
67
|
+
|
|
68
|
+
## Database
|
|
69
|
+
|
|
70
|
+
The database is stored in JSON, in the `map` section. For an example database see `examples/e2e_test_data.json`. The first subsection `data` consists of the actual datapoints, representing points on a map.
|
|
71
|
+
|
|
72
|
+
Datapoints have fields. The next subsections define special types of fields:
|
|
73
|
+
- `obligatory_fields` - here are explicitely stated all the fields that the application assumes are presnt in all datapoints. E.g.
|
|
74
|
+
```
|
|
75
|
+
"position",
|
|
76
|
+
"name",
|
|
77
|
+
"accessible_by"
|
|
78
|
+
```
|
|
79
|
+
TODO: `obligatory_fields` is a new subsection, start using it in the actual application
|
|
80
|
+
- `categories` - fields that can somehow be used in the app, for example by which datapoints can be filtered. Every category has a specified list of allowed values. E.g.
|
|
81
|
+
```
|
|
82
|
+
"accessible_by": ["bikes", "cars", "pedestrians"]
|
|
83
|
+
```
|
|
84
|
+
- `visible_data` - when a datapoint will be rendered as a pin on a map, these fields will be shown in the box when clicking on a pin. E.g.
|
|
85
|
+
```
|
|
86
|
+
"name",
|
|
87
|
+
"type_of_place"
|
|
88
|
+
```
|
|
89
|
+
- `meta-data` - some special data like
|
|
90
|
+
```
|
|
91
|
+
"uuid"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
You can define the fields in all these subsections. Besides these types of fields, there is no restriction on the number of fields a datapoint can have.
|
|
95
|
+
|
|
96
|
+
## Examples
|
|
97
|
+
|
|
98
|
+
You can find examples of working configuration and database in `examples/` directory:
|
|
99
|
+
- `e2e_test_config.yml` - Basic configuration example
|
|
100
|
+
- `e2e_test_data.json` - Example database with sample location data
|
|
101
|
+
- `mongo_e2e_test_config.yml` - MongoDB configuration example
|
|
102
|
+
|
|
103
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import uuid
|
|
3
|
+
|
|
4
|
+
from scipy.spatial import KDTree
|
|
5
|
+
|
|
6
|
+
# Maximum distance to consider a point-cluster match (accounts for floating point errors)
|
|
7
|
+
DISTANCE_THRESHOLD = 1e-8
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def map_clustering_data_to_proper_lazy_loading_object(input_array):
|
|
13
|
+
response_array = []
|
|
14
|
+
for item in input_array:
|
|
15
|
+
if item["count"] == 1:
|
|
16
|
+
response_object = {
|
|
17
|
+
"position": [item["longitude"], item["latitude"]],
|
|
18
|
+
"uuid": item["uuid"],
|
|
19
|
+
"cluster_uuid": None,
|
|
20
|
+
"cluster_count": None,
|
|
21
|
+
"type": "point",
|
|
22
|
+
}
|
|
23
|
+
response_array.append(response_object)
|
|
24
|
+
continue
|
|
25
|
+
response_object = {
|
|
26
|
+
"position": [item["longitude"], item["latitude"]],
|
|
27
|
+
"uuid": None,
|
|
28
|
+
"cluster_uuid": str(uuid.uuid4()),
|
|
29
|
+
"cluster_count": item["count"],
|
|
30
|
+
"type": "cluster",
|
|
31
|
+
}
|
|
32
|
+
response_array.append(response_object)
|
|
33
|
+
return response_array
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Since there can be some floating point errors
|
|
37
|
+
# we need to check if the distance is close enough to 0
|
|
38
|
+
def match_clusters_uuids(points, clusters):
|
|
39
|
+
"""
|
|
40
|
+
Match single-point clusters to their original point UUIDs.
|
|
41
|
+
|
|
42
|
+
For clusters containing exactly one point, this function attempts to match the cluster
|
|
43
|
+
coordinates back to the original point to retrieve its UUID. The 'uuid' key is optional
|
|
44
|
+
and will only be present in single-point clusters where a matching point is found.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
points: List of point dicts with 'position' and 'uuid' keys
|
|
48
|
+
clusters: List of cluster dicts with 'longitude', 'latitude', and 'count' keys.
|
|
49
|
+
For single-point clusters (count=1), a 'uuid' key will be added if a
|
|
50
|
+
matching point is found (modified in place)
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
The modified clusters list with 'uuid' keys added to matched single-point clusters
|
|
54
|
+
"""
|
|
55
|
+
points_coords = [(point["position"][0], point["position"][1]) for point in points]
|
|
56
|
+
tree = KDTree(points_coords)
|
|
57
|
+
for cluster in clusters:
|
|
58
|
+
if cluster["count"] == 1:
|
|
59
|
+
cluster_coords = (cluster["longitude"], cluster["latitude"])
|
|
60
|
+
dist, idx = tree.query(cluster_coords)
|
|
61
|
+
if dist < DISTANCE_THRESHOLD:
|
|
62
|
+
closest_point = points[idx]
|
|
63
|
+
cluster["uuid"] = closest_point["uuid"]
|
|
64
|
+
else:
|
|
65
|
+
# Log warning when no match is found - indicates data inconsistency
|
|
66
|
+
logger.warning(
|
|
67
|
+
"No matching UUID found for cluster at coordinates (%f, %f). "
|
|
68
|
+
"Distance to nearest point: %f (threshold: %f)",
|
|
69
|
+
cluster["longitude"],
|
|
70
|
+
cluster["latitude"],
|
|
71
|
+
dist,
|
|
72
|
+
DISTANCE_THRESHOLD,
|
|
73
|
+
)
|
|
74
|
+
cluster["uuid"] = None
|
|
75
|
+
return clusters
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import typing as t
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
from platzky.config import Config as PlatzkyConfig
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GoodmapConfig(PlatzkyConfig):
|
|
10
|
+
"""Extended configuration for Goodmap with additional frontend library URL."""
|
|
11
|
+
|
|
12
|
+
goodmap_frontend_lib_url: str = Field(
|
|
13
|
+
default="https://cdn.jsdelivr.net/npm/@problematy/goodmap@1.0.4",
|
|
14
|
+
alias="GOODMAP_FRONTEND_LIB_URL",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def model_validate(
|
|
19
|
+
cls,
|
|
20
|
+
obj: t.Any,
|
|
21
|
+
*,
|
|
22
|
+
strict: bool | None = None,
|
|
23
|
+
from_attributes: bool | None = None,
|
|
24
|
+
context: dict[str, t.Any] | None = None,
|
|
25
|
+
) -> "GoodmapConfig":
|
|
26
|
+
"""Override to return correct type for GoodmapConfig."""
|
|
27
|
+
return t.cast(
|
|
28
|
+
"GoodmapConfig",
|
|
29
|
+
super().model_validate(
|
|
30
|
+
obj, strict=strict, from_attributes=from_attributes, context=context
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def parse_yaml(cls, path: str) -> "GoodmapConfig":
|
|
36
|
+
"""Parse YAML configuration file and return GoodmapConfig instance."""
|
|
37
|
+
try:
|
|
38
|
+
with open(path, "r") as f:
|
|
39
|
+
return cls.model_validate(yaml.safe_load(f))
|
|
40
|
+
except FileNotFoundError:
|
|
41
|
+
print(f"Config file not found: {path}", file=sys.stderr)
|
|
42
|
+
raise SystemExit(1)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
|
|
3
|
+
# TODO move filtering to db site
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def does_fulfill_requirement(entry, requirements):
|
|
7
|
+
matches = []
|
|
8
|
+
for category, values in requirements:
|
|
9
|
+
if not values:
|
|
10
|
+
continue
|
|
11
|
+
matches.append(all(entry_value in entry[category] for entry_value in values))
|
|
12
|
+
return all(matches)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def sort_by_distance(data: List[Dict[str, Any]], query_params: Dict[str, List[str]]):
|
|
16
|
+
try:
|
|
17
|
+
if "lat" in query_params and "lon" in query_params:
|
|
18
|
+
lat = float(query_params["lat"][0])
|
|
19
|
+
lon = float(query_params["lon"][0])
|
|
20
|
+
data.sort(key=lambda x: (x["position"][0] - lat) ** 2 + (x["position"][1] - lon) ** 2)
|
|
21
|
+
return data
|
|
22
|
+
return data
|
|
23
|
+
except (ValueError, KeyError, IndexError):
|
|
24
|
+
return data
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def limit(data, query_params):
|
|
28
|
+
try:
|
|
29
|
+
if "limit" in query_params:
|
|
30
|
+
limit = int(query_params["limit"][0])
|
|
31
|
+
data = data[:limit]
|
|
32
|
+
return data
|
|
33
|
+
return data
|
|
34
|
+
except (ValueError, KeyError, IndexError):
|
|
35
|
+
return data
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_queried_data(all_data, categories, query_params):
|
|
39
|
+
requirements = []
|
|
40
|
+
for key in categories.keys():
|
|
41
|
+
requirements.append((key, query_params.get(key)))
|
|
42
|
+
|
|
43
|
+
filtered_data = [x for x in all_data if does_fulfill_requirement(x, requirements)]
|
|
44
|
+
final_data = sort_by_distance(filtered_data, query_params)
|
|
45
|
+
final_data = limit(final_data, query_params)
|
|
46
|
+
return final_data
|