goodmap 1.1.9__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.
@@ -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.9/PKG-INFO ADDED
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.3
2
+ Name: goodmap
3
+ Version: 1.1.9
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
+ ![Github Actions](https://github.com/problematy/goodmap/actions/workflows/release.yml/badge.svg?event=push&branch=main)
38
+ [![Coverage Status](https://coveralls.io/repos/github/Problematy/goodmap/badge.png)](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
+
@@ -0,0 +1,103 @@
1
+ ![Github Actions](https://github.com/problematy/goodmap/actions/workflows/release.yml/badge.svg?event=push&branch=main)
2
+ [![Coverage Status](https://coveralls.io/repos/github/Problematy/goodmap/badge.png)](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