From be64a88053fa378acd7f2dfddf2d5d03ee9febc5 Mon Sep 17 00:00:00 2001 From: Ludo Date: Mon, 27 Jan 2025 17:49:51 +0100 Subject: [PATCH 1/4] add talisman to pre-commit --- .pre-commit-config.yaml | 7 ++++++- back/iarbre_data/management/commands/c02_init_grid.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c37bb04..c78efcc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -94,6 +94,11 @@ repos: repo: https://gitlab.com/devopshq/gitlab-ci-linter rev: v1.0.1 + - repo: https://github.com/thoughtworks/talisman + rev: "v1.32.0" + hooks: + - id: talisman-commit + entry: cmd --githook pre-commit - repo: local hooks: - id: js-lint @@ -118,7 +123,7 @@ repos: hooks: - id: prettier name: prettier - exclude: (back/biophonia/(templates|static)/*)|.json + exclude: (back/iarbre_data/(templates|static)/*)|.json - repo: local hooks: - id: django-test diff --git a/back/iarbre_data/management/commands/c02_init_grid.py b/back/iarbre_data/management/commands/c02_init_grid.py index 842c852..5867d1c 100644 --- a/back/iarbre_data/management/commands/c02_init_grid.py +++ b/back/iarbre_data/management/commands/c02_init_grid.py @@ -263,7 +263,7 @@ def _create_grid_city( tiles_queryset = Tile.objects.filter( geometry__intersects=GEOSGeometry(city.geometry.wkt) ) - if len(tiles_queryset) > 0: # When database is empty + if len(tiles_queryset) > 0: # When database is empty all_ids = load_geodataframe_from_db(tiles_queryset, ["id"]).id total_records = tiles_queryset.count() print(f"Number tiles already in the DB: {total_records}. \n") From d9ddf39fd94775a7f09f691e290ee1b1d1def9b6 Mon Sep 17 00:00:00 2001 From: Ludo Date: Tue, 28 Jan 2025 14:40:57 +0100 Subject: [PATCH 2/4] back: fix: OOM with normalized indice computaiton --- .../c01_compute_plantability_indice.py | 71 ++++++++++--------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/back/plantability/management/commands/c01_compute_plantability_indice.py b/back/plantability/management/commands/c01_compute_plantability_indice.py index 8ab177b..a453020 100644 --- a/back/plantability/management/commands/c01_compute_plantability_indice.py +++ b/back/plantability/management/commands/c01_compute_plantability_indice.py @@ -1,7 +1,11 @@ """Compute and save indice data for the selected cities.""" import pandas as pd +from django.db import transaction from django.core.management import BaseCommand from django.contrib.gis.geos import GEOSGeometry +from django.db.models import Min, Max +from django.db.models import F + from iarbre_data.data_config import FACTORS from iarbre_data.models import Tile, TileFactor @@ -9,7 +13,6 @@ load_geodataframe_from_db, select_city, ) -from iarbre_data.management.commands.c02_init_grid import clean_outside def compute_indice(tiles_id) -> None: @@ -32,41 +35,46 @@ def compute_indice(tiles_id) -> None: factors.name = "factor_coeff" df = df.join(factors, on="factor") df["value"] = df["value"] * df["factor_coeff"] - Tile.objects.bulk_update( - [ - Tile( - id=row.tile_id, - plantability_indice=row.value, - ) - for row in df.itertuples() - ], - ["plantability_indice"], - batch_size=10000, - ) + with transaction.atomic(): + Tile.objects.bulk_update( + [ + Tile( + id=row.tile_id, + plantability_indice=row.value, + ) + for row in df.itertuples() + ], + ["plantability_indice"], + batch_size=10000, + ) def compute_normalized_indice() -> None: """Normalized indice is a score between 0 and 1.""" - tiles = load_geodataframe_from_db(Tile.objects.all(), ["id", "plantability_indice"]) - min_indice = tiles.plantability_indice.min() - max_indice = tiles.plantability_indice.max() - if min_indice is None or max_indice is None or min_indice == max_indice: - print("Normalization not possible (no data or all values are the same).") - return - tiles["plantability_normalized_indice"] = ( - tiles["plantability_indice"] - min_indice - ) / (max_indice - min_indice) - Tile.objects.bulk_update( - [ - Tile( - id=row.id, - plantability_normalized_indice=row.plantability_normalized_indice, + + with transaction.atomic(): + # Calculate the min and max directly in the database + min_indice = Tile.objects.aggregate(Min("plantability_indice"))[ + "plantability_indice__min" + ] + max_indice = Tile.objects.aggregate(Max("plantability_indice"))[ + "plantability_indice__max" + ] + + # Fetch in batches to avoid OOM issues + batch_size = 1e5 + offset = 0 + qs = Tile.objects.all() + while True: + print(f"Batch: {offset+1}") + tiles_batch = qs[offset : offset + batch_size] + if not tiles_batch: + break + Tile.objects.filter(id__in=[tile.id for tile in tiles_batch]).update( + plantability_normalized_indice=(F("plantability_indice") - min_indice) + / (max_indice - min_indice) ) - for row in tiles.itertuples() - ], - ["plantability_normalized_indice"], - batch_size=5000, - ) + offset += 1 class Command(BaseCommand): @@ -86,7 +94,6 @@ def handle(self, *args, **options): insee_code_city = options["insee_code_city"] selected_city = select_city(insee_code_city) - clean_outside(selected_city, int(1e4)) nb_city = len(selected_city) for idx, city in enumerate(selected_city.itertuples()): print(f"{city.name} ({idx+1} on {nb_city} city).") From 0422c62675da95bdcd57983451c8f7a108ba5843 Mon Sep 17 00:00:00 2001 From: Ludo Date: Wed, 29 Jan 2025 14:44:59 +0100 Subject: [PATCH 3/4] back: fix grid generation for hexs --- .../management/commands/c02_init_grid.py | 116 ++++++++++++------ 1 file changed, 79 insertions(+), 37 deletions(-) diff --git a/back/iarbre_data/management/commands/c02_init_grid.py b/back/iarbre_data/management/commands/c02_init_grid.py index 5867d1c..96f64f9 100644 --- a/back/iarbre_data/management/commands/c02_init_grid.py +++ b/back/iarbre_data/management/commands/c02_init_grid.py @@ -18,8 +18,17 @@ from iarbre_data.management.commands.utils import load_geodataframe_from_db +import numpy as np +import gc +import itertools +from shapely.geometry import Point, box +from tqdm import tqdm +from django.db import transaction -def create_squares_for_city(city, grid_size, logger, batch_size=int(1e6)) -> None: + +def create_tiles_for_city( + city, grid_size, tile_shape_cls, logger, batch_size=int(1e6), unit=None, a=1 +) -> None: """ Create square tiles for a specific city and save it to DB. @@ -35,38 +44,31 @@ def create_squares_for_city(city, grid_size, logger, batch_size=int(1e6)) -> Non city_id = city.id city_geom = city.geometry xmin, ymin, xmax, ymax = city_geom.bounds - # Snap bounds to the nearest grid alignment so that all grids are aligned - xmin = np.floor(xmin / grid_size) * grid_size - ymin = np.floor(ymin / grid_size) * grid_size - xmax = np.ceil(xmax / grid_size) * grid_size - ymax = np.ceil(ymax / grid_size) * grid_size + # Bounds for the generation + xmin, ymin, xmax, ymax = tile_shape_cls.adjust_bounds( + xmin, ymin, xmax, ymax, grid_size, unit, a + ) tiles = [] - i = 0 - for x0, y0 in tqdm( - itertools.product( - np.arange(xmin, xmax + grid_size, grid_size), - np.arange(ymin, ymax + grid_size, grid_size), - ) + tile_count = 0 + + for x, (i, y) in tqdm( + tile_shape_cls.tile_positions(xmin, ymin, xmax, ymax, grid_size, unit, a) ): - point = Point([x0, y0]) - if not city_geom.covers(point): + tile = box(x, y * a, x - 2 * grid_size, y * a - 2 * grid_size) + # point = Point(x, y * a) + if not city_geom.intersects(tile): continue i += 1 # Bounds x1 = x0 - grid_size y1 = y0 + grid_size - number_of_decimals = 2 # centimeter-level precision - x0, y0, x1, y1 = map(lambda v: round(v, number_of_decimals), (x0, y0, x1, y1)) - polygon = Polygon.from_bbox([x0, y0, x1, y1]) - polygon.srid = TARGET_PROJ - iris_id = Iris.objects.filter(geometry__intersects=polygon) - if len(iris_id) > 0: - iris_id = iris_id[0].id - else: - iris_id = None - # Init with -1 value + tile_count += 1 + polygon = tile_shape_cls.create_tile_polygon(x, y, grid_size, unit, a, i) + + iris_id = tile_shape_cls.get_iris_id(polygon) + tile = Tile( geometry=polygon, map_geometry=polygon.transform(TARGET_MAP_PROJ, clone=True), @@ -116,18 +118,57 @@ def create_hexs_for_city( xmax = hex_width * (np.ceil(xmax / hex_width) + 1) ymax = hex_height * (np.ceil(ymax / hex_height) + 1) - cols = np.arange(xmin, xmax, 3 * unit) - rows = np.arange(ymin / a, ymax / a, unit) - tiles = [] - tiles_count = 0 - for x, (i, y) in tqdm(itertools.product(cols, enumerate(rows))): - point = Point([x, y * a]) - # Rows are not aligned + @staticmethod + def adjust_bounds(xmin, ymin, xmax, ymax, grid_size, unit=None, a=None): + """Snap bounds to the nearest grid alignment.""" + xmin = np.floor(xmin / grid_size) * grid_size + ymin = np.floor(ymin / grid_size) * grid_size + xmax = np.ceil(xmax / grid_size) * grid_size + ymax = np.ceil(ymax / grid_size) * grid_size + return xmin, ymin, xmax, ymax + + @staticmethod + def tile_positions(xmin, ymin, xmax, ymax, grid_size, unit=None, a=None): + """Generate an iterator for all the position where to create a tile.""" + return itertools.product( + np.arange(xmin, xmax + grid_size, grid_size), + enumerate(np.arange(ymin, ymax + grid_size, grid_size)), + ) + + @staticmethod + def create_tile_polygon(x, y, grid_size, unit=None, a=None, i=None): + """Create a single square and round geometries to optimize storage.""" + x1 = x - grid_size + y1 = y + grid_size + return Polygon.from_bbox([round(x, 2) for x in [x, y, x1, y1]]) + + +class HexTileShape(TileShape): + """Generate hexagonal tile.""" + + @staticmethod + def adjust_bounds(xmin, ymin, xmax, ymax, grid_size, unit, a): + """Snap bounds to the nearest grid alignment.""" + hex_width = 3 * unit + hex_height = 2 * unit * a + xmin = hex_width * (np.floor(xmin / hex_width) - 1) + ymin = hex_height * (np.floor(ymin / hex_height) - 1) + xmax = hex_width * (np.ceil(xmax / hex_width) + 1) + ymax = hex_height * (np.ceil(ymax / hex_height) + 1) + return xmin, ymin, xmax, ymax + + @staticmethod + def tile_positions(xmin, ymin, xmax, ymax, grid_size, unit, a): + """Generate an iterator for all the position where to create a tile.""" + cols = np.arange(xmin, xmax, 3 * unit) + rows = np.arange(ymin / a, ymax / a, unit) + return itertools.product(cols, enumerate(rows)) + + @staticmethod + def create_tile_polygon(x, y, grid_size, unit, a, i): + """Create a single hexagon and round geometries to optimize storage.""" offset = 1.5 * unit if i % 2 != 0 else 0 - x0 = x + offset - if not city_geom.covers(point): - continue - tiles_count += 1 + x0 = x - offset dim = [ (x0, y * a), (x0 + unit, y * a), @@ -161,6 +202,7 @@ def create_hexs_for_city( logger.info(f"Got {len(tiles)} tiles") del tiles[:] gc.collect() + if tiles: # Save last batch Tile.objects.bulk_create(tiles, batch_size=batch_size // 8) @@ -187,7 +229,7 @@ def clean_outside(selected_city, batch_size) -> None: with transaction.atomic(): deleted_count, _ = ( Tile.objects.filter(id__in=batch_ids) - .exclude(geometry__within=city_union_geom.wkt) + .exclude(geometry__intersects=city_union_geom.wkt) .delete() ) total_deleted += deleted_count @@ -326,6 +368,6 @@ def handle(self, *args, **options): ) gc.collect() # just to be sure gc is called... print("Removing duplicates...") - self._remove_duplicates() + remove_duplicates(Tile) if keep_outside is False: clean_outside(selected_city, batch_size) From c288d146aeb46c40a79bf88cacbdab3bbbbcde3d Mon Sep 17 00:00:00 2001 From: Ludo Date: Wed, 29 Jan 2025 14:58:07 +0100 Subject: [PATCH 4/4] doc + fix --- .../management/commands/c02_init_grid.py | 116 ++++++------------ docs/assets/images/banner.png | Bin 0 -> 36809 bytes mkdocs.yml | 4 +- 3 files changed, 39 insertions(+), 81 deletions(-) create mode 100644 docs/assets/images/banner.png diff --git a/back/iarbre_data/management/commands/c02_init_grid.py b/back/iarbre_data/management/commands/c02_init_grid.py index 96f64f9..5867d1c 100644 --- a/back/iarbre_data/management/commands/c02_init_grid.py +++ b/back/iarbre_data/management/commands/c02_init_grid.py @@ -18,17 +18,8 @@ from iarbre_data.management.commands.utils import load_geodataframe_from_db -import numpy as np -import gc -import itertools -from shapely.geometry import Point, box -from tqdm import tqdm -from django.db import transaction - -def create_tiles_for_city( - city, grid_size, tile_shape_cls, logger, batch_size=int(1e6), unit=None, a=1 -) -> None: +def create_squares_for_city(city, grid_size, logger, batch_size=int(1e6)) -> None: """ Create square tiles for a specific city and save it to DB. @@ -44,31 +35,38 @@ def create_tiles_for_city( city_id = city.id city_geom = city.geometry xmin, ymin, xmax, ymax = city_geom.bounds - # Bounds for the generation - xmin, ymin, xmax, ymax = tile_shape_cls.adjust_bounds( - xmin, ymin, xmax, ymax, grid_size, unit, a - ) + # Snap bounds to the nearest grid alignment so that all grids are aligned + xmin = np.floor(xmin / grid_size) * grid_size + ymin = np.floor(ymin / grid_size) * grid_size + xmax = np.ceil(xmax / grid_size) * grid_size + ymax = np.ceil(ymax / grid_size) * grid_size tiles = [] - tile_count = 0 - - for x, (i, y) in tqdm( - tile_shape_cls.tile_positions(xmin, ymin, xmax, ymax, grid_size, unit, a) + i = 0 + for x0, y0 in tqdm( + itertools.product( + np.arange(xmin, xmax + grid_size, grid_size), + np.arange(ymin, ymax + grid_size, grid_size), + ) ): - tile = box(x, y * a, x - 2 * grid_size, y * a - 2 * grid_size) - # point = Point(x, y * a) - if not city_geom.intersects(tile): + point = Point([x0, y0]) + if not city_geom.covers(point): continue i += 1 # Bounds x1 = x0 - grid_size y1 = y0 + grid_size - tile_count += 1 - polygon = tile_shape_cls.create_tile_polygon(x, y, grid_size, unit, a, i) - - iris_id = tile_shape_cls.get_iris_id(polygon) - + number_of_decimals = 2 # centimeter-level precision + x0, y0, x1, y1 = map(lambda v: round(v, number_of_decimals), (x0, y0, x1, y1)) + polygon = Polygon.from_bbox([x0, y0, x1, y1]) + polygon.srid = TARGET_PROJ + iris_id = Iris.objects.filter(geometry__intersects=polygon) + if len(iris_id) > 0: + iris_id = iris_id[0].id + else: + iris_id = None + # Init with -1 value tile = Tile( geometry=polygon, map_geometry=polygon.transform(TARGET_MAP_PROJ, clone=True), @@ -118,57 +116,18 @@ def create_hexs_for_city( xmax = hex_width * (np.ceil(xmax / hex_width) + 1) ymax = hex_height * (np.ceil(ymax / hex_height) + 1) - @staticmethod - def adjust_bounds(xmin, ymin, xmax, ymax, grid_size, unit=None, a=None): - """Snap bounds to the nearest grid alignment.""" - xmin = np.floor(xmin / grid_size) * grid_size - ymin = np.floor(ymin / grid_size) * grid_size - xmax = np.ceil(xmax / grid_size) * grid_size - ymax = np.ceil(ymax / grid_size) * grid_size - return xmin, ymin, xmax, ymax - - @staticmethod - def tile_positions(xmin, ymin, xmax, ymax, grid_size, unit=None, a=None): - """Generate an iterator for all the position where to create a tile.""" - return itertools.product( - np.arange(xmin, xmax + grid_size, grid_size), - enumerate(np.arange(ymin, ymax + grid_size, grid_size)), - ) - - @staticmethod - def create_tile_polygon(x, y, grid_size, unit=None, a=None, i=None): - """Create a single square and round geometries to optimize storage.""" - x1 = x - grid_size - y1 = y + grid_size - return Polygon.from_bbox([round(x, 2) for x in [x, y, x1, y1]]) - - -class HexTileShape(TileShape): - """Generate hexagonal tile.""" - - @staticmethod - def adjust_bounds(xmin, ymin, xmax, ymax, grid_size, unit, a): - """Snap bounds to the nearest grid alignment.""" - hex_width = 3 * unit - hex_height = 2 * unit * a - xmin = hex_width * (np.floor(xmin / hex_width) - 1) - ymin = hex_height * (np.floor(ymin / hex_height) - 1) - xmax = hex_width * (np.ceil(xmax / hex_width) + 1) - ymax = hex_height * (np.ceil(ymax / hex_height) + 1) - return xmin, ymin, xmax, ymax - - @staticmethod - def tile_positions(xmin, ymin, xmax, ymax, grid_size, unit, a): - """Generate an iterator for all the position where to create a tile.""" - cols = np.arange(xmin, xmax, 3 * unit) - rows = np.arange(ymin / a, ymax / a, unit) - return itertools.product(cols, enumerate(rows)) - - @staticmethod - def create_tile_polygon(x, y, grid_size, unit, a, i): - """Create a single hexagon and round geometries to optimize storage.""" + cols = np.arange(xmin, xmax, 3 * unit) + rows = np.arange(ymin / a, ymax / a, unit) + tiles = [] + tiles_count = 0 + for x, (i, y) in tqdm(itertools.product(cols, enumerate(rows))): + point = Point([x, y * a]) + # Rows are not aligned offset = 1.5 * unit if i % 2 != 0 else 0 - x0 = x - offset + x0 = x + offset + if not city_geom.covers(point): + continue + tiles_count += 1 dim = [ (x0, y * a), (x0 + unit, y * a), @@ -202,7 +161,6 @@ def create_tile_polygon(x, y, grid_size, unit, a, i): logger.info(f"Got {len(tiles)} tiles") del tiles[:] gc.collect() - if tiles: # Save last batch Tile.objects.bulk_create(tiles, batch_size=batch_size // 8) @@ -229,7 +187,7 @@ def clean_outside(selected_city, batch_size) -> None: with transaction.atomic(): deleted_count, _ = ( Tile.objects.filter(id__in=batch_ids) - .exclude(geometry__intersects=city_union_geom.wkt) + .exclude(geometry__within=city_union_geom.wkt) .delete() ) total_deleted += deleted_count @@ -368,6 +326,6 @@ def handle(self, *args, **options): ) gc.collect() # just to be sure gc is called... print("Removing duplicates...") - remove_duplicates(Tile) + self._remove_duplicates() if keep_outside is False: clean_outside(selected_city, batch_size) diff --git a/docs/assets/images/banner.png b/docs/assets/images/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..4875cdb85699a1de40991575d242c8ab68de535e GIT binary patch literal 36809 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 literal 0 HcmV?d00001 diff --git a/mkdocs.yml b/mkdocs.yml index c4f71b1..b81ba18 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,4 +1,4 @@ -site_name: IA.rbre +site_name: Documentation IA.rbre site_url: "https://docs.iarbre.fr/" repo_name: "Telescoop/iarbre" repo_url: "https://github.com/TelesCoop/iarbre" @@ -65,7 +65,7 @@ theme: - tabs - content.tabs.link custom_dir: "docs/theme/" - logo: "assets/images/logo.png" + logo: "assets/images/banner.png" favicon: "assets/images/logo.png" palette: primary: custom