diff --git a/README.md b/README.md index ec1aa216..dbf3a898 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,21 @@ AI for precise geospatial data analysis and visualization. pip install blue-geo ``` -πŸ”· [ukraine-timemap](#ukraine-timemap-) πŸ‡ΊπŸ‡¦ πŸ”· +πŸ”· [QGIS](#QGIS) πŸ”· [ukraine-timemap](#ukraine-timemap-) πŸ‡ΊπŸ‡¦ πŸ”· + +--- + +## QGIS + +to start, generate the seed 🌱, + +```bash +QGIS seed +``` + +and paste it in the Python Console in QGIS. + +![image](https://github.com/kamangir/assets/blob/main/blue-geo/QGIS-python-console.png?raw=true) --- diff --git a/blue_geo/.abcli/QGIS.sh b/blue_geo/.abcli/QGIS.sh new file mode 100644 index 00000000..12b95399 --- /dev/null +++ b/blue_geo/.abcli/QGIS.sh @@ -0,0 +1,49 @@ +#! /usr/bin/env bash + +export abcli_QGIS_path_profile="$HOME/Library/Application Support/QGIS/QGIS3/profiles/default" +export abcli_QGIS_path_expressions=$abcli_QGIS_path_profile/python/expressions +export abcli_QGIS_path_expressions_git=$abcli_path_git/blue-geo/blue_geo/QGIS/expressions +export abcli_QGIS_path_templates=$abcli_QGIS_path_profile/project_templates +export abcli_QGIS_path_shared=$HOME/Downloads/QGIS +export abcli_QGIS_path_server=$abcli_QGIS_path_shared/server + +mkdir -p $abcli_QGIS_path_server + +function QGIS() { + blue_geo_QGIS "$@" +} + +# internal function to abcli_seed. +function QGIS_seed() { + # seed is NOT local + seed=$(python3 -m blue_geo.QGIS generate_seed) +} + +function blue_geo_QGIS() { + local task=$(abcli_unpack_keyword $1 help) + + if [ $task == "help" ]; then + abcli_show_usage "QGIS seed [screen]" \ + "seed 🌱 QGIS." + + blue_geo_QGIS_expressions "$@" + blue_geo_QGIS_server "$@" + return + fi + + local function_name=blue_geo_QGIS_$task + if [[ $(type -t $function_name) == "function" ]]; then + $function_name "${@:2}" + return + fi + + if [ "$task" == "seed" ]; then + abcli_seed QGIS "${@:2}" + return + fi + + abcli_log_error "-QGIS: $task: command not found." + return 1 +} + +abcli_source_path - caller,suffix=/QGIS diff --git a/blue_geo/.abcli/QGIS/expressions.sh b/blue_geo/.abcli/QGIS/expressions.sh new file mode 100644 index 00000000..3f2b465d --- /dev/null +++ b/blue_geo/.abcli/QGIS/expressions.sh @@ -0,0 +1,47 @@ +#! /usr/bin/env bash + +function blue_geo_QGIS_expressions() { + local task=$1 + + if [ "$task" == help ]; then + abcli_show_usage "QGIS expressions pull" \ + "pull QGIS expressions." + abcli_show_usage "QGIS expressions push [push]" \ + "push QGIS expressions." + + abcli_log " πŸ“‚ $abcli_QGIS_path_expressions" + abcli_log " πŸ“‚ $abcli_QGIS_path_expressions_git" + return + fi + + if [[ "$task" == pull ]]; then + rsync -av \ + "$abcli_QGIS_path_expressions_git/" \ + "$abcli_QGIS_path_expressions/" + return + fi + + if [[ "$task" == push ]]; then + local options=$2 + local do_push=$(abcli_option_int "$options" push 0) + + rsync -av \ + --exclude='__pycache__' \ + --exclude='default.py' \ + --exclude='__init__.py' \ + "$abcli_QGIS_path_expressions/" \ + "$abcli_QGIS_path_expressions_git/" + + if [[ "$do_push" == 1 ]]; then + abcli_git blue-geo push \ + "$(python3 -m blue_geo version) QGIS expressions" + else + abcli_git blue-geo status ~all + fi + + return + fi + + abcli_log_error "-QGIS: expressions: $task: command not found." + return 1 +} diff --git a/blue_geo/.abcli/QGIS/server.sh b/blue_geo/.abcli/QGIS/server.sh new file mode 100644 index 00000000..3dedf44b --- /dev/null +++ b/blue_geo/.abcli/QGIS/server.sh @@ -0,0 +1,34 @@ +#! /usr/bin/env bash + +function blue_geo_QGIS_serve() { + blue_geo_QGIS_server "$@" +} + +function blue_geo_QGIS_server() { + local options=$1 + if [ $(abcli_option_int "$options" help 0) == 1 ]; then + abcli_show_usage "QGIS serve[r] [start]" \ + "start QGIS server." + return + fi + + local prompt="🌐 $(blue_geo version).QGIS server ... (^C to stop)" + abcli_log $prompt + + local filename + cd $abcli_QGIS_path_server + while true; do + sleep 1 + for filename in *.command; do + if [ -e "$filename" ]; then + local command=$(cat $filename) + abcli_log "$filename: $command" + + abcli_eval - "$command" + rm -v $filename + + abcli_log $prompt + fi + done + done +} diff --git a/blue_geo/QGIS/__init__.py b/blue_geo/QGIS/__init__.py new file mode 100644 index 00000000..810faa4a --- /dev/null +++ b/blue_geo/QGIS/__init__.py @@ -0,0 +1,3 @@ +from blue_geo import NAME + +NAME = f"{NAME}.QGIS" diff --git a/blue_geo/QGIS/__main__.py b/blue_geo/QGIS/__main__.py new file mode 100644 index 00000000..98d6854b --- /dev/null +++ b/blue_geo/QGIS/__main__.py @@ -0,0 +1,28 @@ +import argparse +from blue_geo.QGIS import NAME +from blue_geo import VERSION +from blue_geo.QGIS.seed import generate_seed +from blue_geo.logger import logger +from blueness.argparse.generic import sys_exit + + +parser = argparse.ArgumentParser( + f"python3 -m {NAME}", + description=f"{NAME}-{VERSION}", +) +parser.add_argument( + "task", + type=str, + help="generate_seed", +) +args = parser.parse_args() + +success = False +if args.task == "generate_seed": + success = seed = generate_seed() + if success: + print(seed) +else: + success = None + +sys_exit(logger, NAME, args.task, success) diff --git a/blue_geo/QGIS/console/QGIS.py b/blue_geo/QGIS/console/QGIS.py new file mode 100644 index 00000000..b93d3092 --- /dev/null +++ b/blue_geo/QGIS/console/QGIS.py @@ -0,0 +1,258 @@ +import os +import time +import random +from tqdm import tqdm + + +if not QGIS_is_live: + from log import log, log_error, verbose + from layer import layer + from project import project + from seed import seed + +NAME = "blue_geo.QGIS" + +VERSION = "5.14.1" + +abcli_object_root = os.path.join( + os.getenv("HOME", ""), + "storage/abcli", +) + + +class ABCLI_QGIS(object): + def __init__(self): + self.app_list = [] + self.object_name = self.timestamp() + + def add_application(self, app): + self.app_list += [app] + + def intro(self): + log( + "{}-{}: {}".format( + NAME, + VERSION, + ", ".join( + [ + "{} {}".format( + app.name, + app.icon, + ) + for app in self.app_list + ] + ), + ) + ) + log( + 'Type in "{}.help()" for help.'.format( + "|".join(["Q"] + [app.name for app in self.app_list]) + ) + ) + + def clear(self): + # https://gis.stackexchange.com/a/216444/210095 + from qgis.PyQt.QtWidgets import QDockWidget + + consoleWidget = iface.mainWindow().findChild(QDockWidget, "PythonConsole") + consoleWidget.console.shellOut.clearConsole() + + self.intro() + + def create_video(self, filename="QGIS", object_name=""): + seed( + [ + "abcli", + "create_video", + f"png,fps=2,filename={filename},gif", + object_name if object_name else self.object_name, + ] + ) + + def export(self, filename="", object_name=""): + filename = self.file_path( + filename=filename if filename else "{}.png".format(self.timestamp()), + object_name=object_name, + ) + + qgis.utils.iface.mapCanvas().saveAsImage(filename) + log(filename, icon="πŸ–ΌοΈ") + + def file_path(self, filename, object_name=""): + return os.path.join(self.object_path(object_name), filename) + + def find_layer(self, layer_name): + return QgsProject.instance().mapLayersByName(layer_name) + + def help(self, clear=False): + if clear: + self.clear() + + log("object", self.object_name, icon="πŸ“‚") + log("Q.clear()", "clear Python Console.") + log("Q.create_video()", "create a video.") + layer.help() + if verbose: + log("Q.export([filename],[object_name])", "export.") + log("Q.list_of_layers()", "list of layers.") + log("Q.load(filename,layer_name,template_name)", "load a layer.") + log('Q.open("||layer|object|project")', "open.") + project.help() + if verbose: + log("Q.refresh()", "refresh.") + log("Q.reload()", "reload all layers.") + log('Q.select("")', "select .") + if verbose: + log("Q.unload(layer_name)", "unload layer_name.") + log('Q.upload("||layer|project|qgz")', "upload.") + log("verbose=True|False", "set verbose state.") + + for app in self.app_list: + app.help() + + def list_of_layers(self, aux=False): + output = [ + layer_.name() for layer_ in QgsProject.instance().mapLayers().values() + ] + if not aux: + output = [ + layer_name + for layer_name in output + if not layer_name.startswith("Google") + and not layer_name.startswith("template") + ] + log( + "{} layer(s){}".format( + len(output), + ": {}".format(", ".join(output)) if verbose else "", + ), + icon="πŸ”Ž", + ) + return output + + def load( + self, + filename, + layer_name, + template_name="", + refresh=True, + ): + if len(self.find_layer(layer_name)) > 0: + print(f"βœ… {layer_name}") + return True + + if filename.endswith(".geojson"): + layer_ = QgsVectorLayer(filename, layer_name, "ogr") + elif filename.endswith(".tif"): + layer_ = QgsRasterLayer(filename, layer_name) + else: + log_error(f"cannot load {filename}.") + return False + + if not layer_.isValid(): + log_error(f"invalid layer: {filename}.") + return False + + QgsProject.instance().addMapLayer(layer_) + + if template_name: + template_layer = self.find_layer(template_name) + if not len(template_layer): + log_error(f"template not found: {template_name}.") + return False + + # https://gis.stackexchange.com/a/357206/210095 + source_style = QgsMapLayerStyle() + source_style.readFromLayer(template_layer[0]) + source_style.writeToLayer(layer_) + layer_.triggerRepaint() + + log( + layer_name, + template_name, + icon="🎨", + ) + + if refresh: + self.refresh() + + def object_path(self, object_name=""): + output = os.path.join( + abcli_object_root, + object_name if object_name else self.object_name, + ) + os.makedirs(output, exist_ok=True) + return output + + def open(self, what="object"): + self.open_folder( + layer.path + if what in "layer" + else self.object_path() if what == "object" else project.path + ) + + def open_folder(self, path): + if not path: + log_error("path not found.") + return + + log(path) + os.system(f"open {path}") + + def refresh(self, deep=False): + log("{}refresh.".format("deep" if deep else "")) + if deep: + # https://api.qgis.org/api/classQgsMapCanvas.html + iface.mapCanvas().redrawAllLayers() + else: + iface.mapCanvas().refresh() + + def reload(self): + # https://gis.stackexchange.com/a/449101/210095 + for layer_ in tqdm(QgsProject.instance().mapLayers().values()): + layer_.dataProvider().reloadData() + + def select(self, object_name=""): + self.object_name = object_name if object_name else self.timestamp() + log(f"object_name: {self.object_name}", icon="πŸ“‚") + log(f"object_path: {self.object_path()}", icon="πŸ“‚") + + os.makedirs(self.object_path(), exist_ok=True) + + def timestamp(self): + return time.strftime( + f"%Y-%m-%d-%H-%M-%S-{random.randrange(100000):05d}", + time.localtime(time.time()), + ) + + def unload(self, layer_name, refresh=True): + log(layer_name, icon="πŸ—‘οΈ") + + for layer_ in self.find_layer(layer_name): + QgsProject.instance().removeMapLayer(layer_.id()) + + if refresh: + self.refresh() + + def upload(self, object_name=""): + seed( + [ + "abcli", + "upload", + f"filename={project.name}.qgz" if object_name == "qgz" else "-", + ( + project.name + if object_name in ["project", "qgz", project] + else ( + layer.object_name + if object_name in ["layer", layer] + else object_name if object_name else self.object_name + ) + ), + ] + ) + + +QGIS = ABCLI_QGIS() + +Q = QGIS diff --git a/blue_geo/QGIS/console/application.py b/blue_geo/QGIS/console/application.py new file mode 100644 index 00000000..9f80f64d --- /dev/null +++ b/blue_geo/QGIS/console/application.py @@ -0,0 +1,13 @@ +if not QGIS_is_live: + from log import log + + +class ROOFAI_QGIS_APPLICATION(object): + def __init__(self, name, icon): + self.name = name + self.icon = icon + + log(self.name, "", icon=self.icon) + + def log(self, message, note=""): + log(message, note, icon=self.icon) diff --git a/blue_geo/QGIS/console/apps/template.py b/blue_geo/QGIS/console/apps/template.py new file mode 100644 index 00000000..6f363b22 --- /dev/null +++ b/blue_geo/QGIS/console/apps/template.py @@ -0,0 +1,17 @@ +if not QGIS_is_live: + from application import ROOFAI_QGIS_APPLICATION + + +class ROOFAI_QGIS_APPLICATION_TEMPLATE(ROOFAI_QGIS_APPLICATION): + def __init__(self): + super().__init__("template", "πŸŒ€") + + def help(self): + self.log( + "template.func(var)", + "func.", + ) + + def func(self, var: str = "πŸͺ„"): + self.log(var) + diff --git a/blue_geo/QGIS/console/apps/ukraine_timemap.py b/blue_geo/QGIS/console/apps/ukraine_timemap.py new file mode 100644 index 00000000..7bfb9d0a --- /dev/null +++ b/blue_geo/QGIS/console/apps/ukraine_timemap.py @@ -0,0 +1,27 @@ +if not QGIS_is_live: + from application import ROOFAI_QGIS_APPLICATION + from project import project + from seed import seed + + +class ROOFAI_QGIS_APPLICATION_UKRAINE_TIMEMAP(ROOFAI_QGIS_APPLICATION): + def __init__(self): + super().__init__("ukraine_timemap", "πŸ‡ΊπŸ‡¦") + + def help(self): + self.log( + 'ukraine_timemap.update("dryrun,~upload")', + f"update {project.name}.", + ) + + def update(self, options: str = ""): + self.log(f"ukraine_timemap.update({options}): {project.name}") + + seed( + [ + "ukraine_timemap", + "ingest", + f"~copy_template,{options}", + project.name, + ] + ) diff --git a/blue_geo/QGIS/console/apps/vanwatch.py b/blue_geo/QGIS/console/apps/vanwatch.py new file mode 100644 index 00000000..551a383a --- /dev/null +++ b/blue_geo/QGIS/console/apps/vanwatch.py @@ -0,0 +1,87 @@ +import os +import glob + +if not QGIS_is_live: + from application import ROOFAI_QGIS_APPLICATION + from log import log + from project import project + from QGIS import QGIS + + +class ROOFAI_QGIS_APPLICATION_VANWATCH(ROOFAI_QGIS_APPLICATION): + def __init__(self): + super().__init__("vanwatch", "🌈") + + def help(self): + self.log( + "vanwatch.list_layers()", + "list vanwatch layers.", + ) + self.log( + "vanwatch.load(count=, timed=True, offset=, prefix=)", + "load layers.", + ) + self.log( + "vanwatch.unload(prefix)", + "unload prefix*.", + ) + + def list_layers(self): + return sorted( + [ + os.path.splitext(os.path.basename(filename))[0] + for filename in glob.glob( + os.path.join( + project.path, + "*.geojson", + ) + ) + ] + ) + + def load( + self, + prefix="", + count=-1, + offset=0, + refresh=True, + timed=False, + ) -> bool: + list_layers = self.list_layers() + + list_layers = list(reversed(list_layers)) + if offset: + list_layers = list_layers[offset:] + if count != -1: + list_layers = list_layers[:count] + + for layer_name in list_layers: + if not layer_name.startswith(prefix): + continue + + filename = os.path.join(project.path, f"{layer_name}.geojson") + + QGIS.load( + filename, + layer_name, + "template-timed" if timed else "template", + refresh=False, + ) + + self.log(f"loaded {len(list_layers)} layer(s).") + + if refresh: + QGIS.refresh() + + def unload(self, prefix="", refresh=True): + log(prefix, icon="πŸ—‘οΈ") + + for layer_name in [ + layer_name + for layer_name in QGIS.list_of_layers() + if layer_name.startswith(prefix) + ]: + QGIS.unload(layer_name, refresh=False) + + if refresh: + QGIS.refresh() diff --git a/blue_geo/QGIS/console/layer.py b/blue_geo/QGIS/console/layer.py new file mode 100644 index 00000000..d11ee5e5 --- /dev/null +++ b/blue_geo/QGIS/console/layer.py @@ -0,0 +1,34 @@ +import os + +if not QGIS_is_live: + from log import log_error + + +class ABCLI_QGIS_Layer(object): + def help(self): + pass + + @property + def filename(self): + try: + return iface.activeLayer().dataProvider().dataSourceUri() + except: + log_error("unknown layer.filename.") + return "" + + @property + def name(self): + filename = self.filename + return filename.split(os.sep)[-1].split(".")[0] if filename else "" + + @property + def object_name(self): + filename = self.filename + return filename.split(os.sep)[-2] if filename else "" + + @property + def path(self): + return os.path.dirname(self.filename) + + +layer = ABCLI_QGIS_Layer() diff --git a/blue_geo/QGIS/console/log.py b/blue_geo/QGIS/console/log.py new file mode 100644 index 00000000..2856000c --- /dev/null +++ b/blue_geo/QGIS/console/log.py @@ -0,0 +1,19 @@ +verbose = False + +QGIS_is_live = True + + +def log(message, note="", icon="🌐"): + print( + "{} {}{}".format( + icon, + (f"{message:.<40}" if len(message) < 38 else f"{message}\n {40*'.'}") + if note + else message, + note, + ) + ) + + +def log_error(message, note=""): + log(message, note, icon="❗️") diff --git a/blue_geo/QGIS/console/main.py b/blue_geo/QGIS/console/main.py new file mode 100644 index 00000000..71b31c44 --- /dev/null +++ b/blue_geo/QGIS/console/main.py @@ -0,0 +1,18 @@ +if not QGIS_is_live: + from QGIS import QGIS + from apps.vanwatch import ROOFAI_QGIS_APPLICATION_VANWATCH + from apps.template import ROOFAI_QGIS_APPLICATION_TEMPLATE + from apps.ukraine_timemap import ROOFAI_QGIS_APPLICATION_UKRAINE_TIMEMAP + + +vanwatch = ROOFAI_QGIS_APPLICATION_VANWATCH() +QGIS.add_application(vanwatch) + +template = ROOFAI_QGIS_APPLICATION_TEMPLATE() +QGIS.add_application(template) + +ukraine_timemap = ROOFAI_QGIS_APPLICATION_UKRAINE_TIMEMAP() +ukraine = ukraine_timemap +QGIS.add_application(ukraine_timemap) + +QGIS.intro() diff --git a/blue_geo/QGIS/console/project.py b/blue_geo/QGIS/console/project.py new file mode 100644 index 00000000..6634b02e --- /dev/null +++ b/blue_geo/QGIS/console/project.py @@ -0,0 +1,17 @@ +import os + + +class ABCLI_QGIS_Project(object): + def help(self): + pass + + @property + def name(self): + return QgsProject.instance().homePath().split(os.sep)[-1] + + @property + def path(self): + return QgsProject.instance().homePath() + + +project = ABCLI_QGIS_Project() diff --git a/blue_geo/QGIS/console/seed.py b/blue_geo/QGIS/console/seed.py new file mode 100644 index 00000000..e21c2b55 --- /dev/null +++ b/blue_geo/QGIS/console/seed.py @@ -0,0 +1,35 @@ +import time +from typing import Union, List +import os +import random + +if not QGIS_is_live: + from log import log + +blue_geo_QGIS_path_server = os.path.join( + os.getenv("HOME", ""), + "Downloads/QGIS/server", +) + +os.makedirs(blue_geo_QGIS_path_server, exist_ok=True) + + +def seed(command: Union[str, List[str]]): + if isinstance(command, list): + command = " ".join(command) + + hash_id = "{}-{:05d}".format( + time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time())), + random.randrange(100000), + ) + + with open( + os.path.join( + blue_geo_QGIS_path_server, + f"{hash_id}.command", + ), + "w", + ) as f: + f.write(command) + + log(hash_id, command, icon="🌱") diff --git a/blue_geo/QGIS/console/utils.py b/blue_geo/QGIS/console/utils.py new file mode 100644 index 00000000..29d3c9c1 --- /dev/null +++ b/blue_geo/QGIS/console/utils.py @@ -0,0 +1,10 @@ +if not QGIS_is_live: + from QGIS import QGIS + + +def clear(): + QGIS.clear() + + +def upload(object_name=""): + QGIS.upload(object_name) diff --git a/blue_geo/QGIS/expressions/ukraine_timemap_display.py b/blue_geo/QGIS/expressions/ukraine_timemap_display.py new file mode 100644 index 00000000..23bec792 --- /dev/null +++ b/blue_geo/QGIS/expressions/ukraine_timemap_display.py @@ -0,0 +1,29 @@ +from qgis.core import * +from qgis.gui import * + + +@qgsfunction(args="auto", group="Custom", referenced_columns=[]) +def ukraine_timemap_display(project_filename, id, date, description, feature, parent): + """ + produce display text for a ukraine_timemap mapid. + + ukraine_timemap_display( + "id", + "description", + "date", + ) + """ + version = "1.9.1" + + return "
".join( + [ + description, + '{} | {} | #{}'.format( + project_filename.split(".")[0], + date.toString("yyyy-MM-dd"), + id, + id, + ), + f"πŸ‡ΊπŸ‡¦ ukraine timemap template {version}", + ] + ) diff --git a/blue_geo/QGIS/expressions/vanwatch_display.py b/blue_geo/QGIS/expressions/vanwatch_display.py new file mode 100644 index 00000000..32ba9675 --- /dev/null +++ b/blue_geo/QGIS/expressions/vanwatch_display.py @@ -0,0 +1,47 @@ +from qgis.core import * +from qgis.gui import * + +# version 2.1.1 + + +@qgsfunction(args="auto", group="Custom", referenced_columns=[]) +def vanwatch_display(object_name, cameras, feature, parent): + """ + Produce display text for a vanwatch mapid. + + vanwatch_display( + layer_property(@layer,'name'), + "cameras" + ) + """ + object_name = object_name.split(" ")[0] + + url_prefix = "https://kamangir-public.s3.ca-central-1.amazonaws.com" + + image_name_list = [url.split("/")[-1].split(".")[0] for url in cameras.split(",")] + + url_list = [ + "{}/{}/{}-inference.jpg".format( + url_prefix, + object_name, + image_name, + ) + for image_name in image_name_list + ] + + image_tag_list = [ + f'' for url in url_list + ] + + return "\n".join( + [ + '', + " ", + ] + + [f" " for image_tag in image_tag_list] + + [ + " ", + "
{image_tag}
", + object_name, + ] + ) diff --git a/blue_geo/QGIS/expressions/vanwatch_label.py b/blue_geo/QGIS/expressions/vanwatch_label.py new file mode 100644 index 00000000..5afd7706 --- /dev/null +++ b/blue_geo/QGIS/expressions/vanwatch_label.py @@ -0,0 +1,56 @@ +import math +from qgis.core import * +from qgis.gui import * + +# version 2.1.1 + + +@qgsfunction(args="auto", group="Custom", referenced_columns=[]) +def vanwatch_label(row, webdings, feature, parent): + """ + Produce label text for a vanwatch mapid. + + vanwatch_label(row) + """ + row = { + keyword: value + for keyword, value in row.items() + if isinstance(value, int) and value != 0 + } + + symbol = { + "bicycle": "", + "car": "ο‚Ž", + "person": "ο‚€", + } + scale = 10 + + if webdings: + output = "".join( + [ + thing + for thing in [ + "".join( + math.ceil(row.get(thing, 0) / scale) * [symbol.get(thing, "x")] + ) + for thing in symbol.keys() + ] + if thing + ] + ) + if not output: + return output + + side = math.ceil(math.sqrt(len(output))) + matrix = {0: []} + j = 0 + for i in range(len(output)): + matrix[j] += [output[i]] + if len(matrix[j]) >= side: + j += 1 + matrix[j] = [] + return "\n".join(["".join(thing) for thing in matrix.values()]) + + return ", ".join( + sorted(["{}:{}".format(keyword, value) for keyword, value in row.items()]) + ) diff --git a/blue_geo/QGIS/expressions/vanwatch_temporal.py b/blue_geo/QGIS/expressions/vanwatch_temporal.py new file mode 100644 index 00000000..11ce7dac --- /dev/null +++ b/blue_geo/QGIS/expressions/vanwatch_temporal.py @@ -0,0 +1,31 @@ +import os +from qgis.core import * +from qgis.gui import * + +# version 2.4.1 + + +@qgsfunction(args="auto", group="Custom", referenced_columns=[]) +def vanwatch_temporal(layer_name, project_path, feature, parent): + """ + Calculates the sum of the two parameters value1 and value2. +

Example usage:

+
    +
  • my_sum(5, 8) -> 13
  • +
  • my_sum("field1", "field2") -> 42
  • +
+ """ + list_of_layers = [] + for _, _, files in os.walk(os.path.dirname(project_path)): + for filename in files: + if filename.endswith(".geojson"): + list_of_layers += [filename.split(".")[0]] + + list_of_layers = sorted(list_of_layers) + + if layer_name not in list_of_layers: + return "x1" + + index = list_of_layers.index(layer_name) + + return list_of_layers[index + 1] if index < len(list_of_layers) - 1 else "x2" diff --git a/blue_geo/QGIS/seed.py b/blue_geo/QGIS/seed.py new file mode 100644 index 00000000..9b41964c --- /dev/null +++ b/blue_geo/QGIS/seed.py @@ -0,0 +1,25 @@ +def generate_seed() -> str: + list_of_modules = [ + "log", + "project", + "layer", + "application", + "seed", + "QGIS", + "apps/vanwatch", + "apps/template", + "apps/ukraine_timemap", + "main", + "utils", + ] + + seed = "; ".join( + [ + 'exec(Path(f\'{os.getenv("HOME")}/git/blue-geo/blue_geo/QGIS/console/' + + module_name + + ".py').read_text())" + for module_name in list_of_modules + ] + ) + + return seed diff --git a/blue_geo/__init__.py b/blue_geo/__init__.py index a9790775..aea883e1 100644 --- a/blue_geo/__init__.py +++ b/blue_geo/__init__.py @@ -4,6 +4,6 @@ DESCRIPTION = f"{ICON} AI for precise geospatial data analysis and visualization." -VERSION = "4.11.1" +VERSION = "4.12.1" REPO_NAME = "blue-geo"