diff --git a/flows.json b/flows.json index d8c5eb1..b79c1f8 100644 --- a/flows.json +++ b/flows.json @@ -1,4 +1,12 @@ [ + { + "id": "ae9e82a0a0e1eb89", + "type": "tab", + "label": "Cálculos de H3", + "disabled": false, + "info": "", + "env": [] + }, { "id": "f511dd2230a153e7", "type": "tab", @@ -7,6 +15,173 @@ "info": "", "env": [] }, + { + "id": "748b5921246ec468", + "type": "postgreSQLConfig", + "name": "PostGIS", + "host": "10.10.5.32", + "hostFieldType": "str", + "port": 5432, + "portFieldType": "num", + "database": "postgres", + "databaseFieldType": "str", + "ssl": "false", + "sslFieldType": "bool", + "applicationName": "", + "applicationNameType": "str", + "max": "500", + "maxFieldType": "num", + "idle": "100000", + "idleFieldType": "num", + "connectionTimeout": "300000", + "connectionTimeoutFieldType": "num", + "user": "postgres", + "userFieldType": "str", + "password": "tfmuocdfcarvajal", + "passwordFieldType": "str" + }, + { + "id": "3005f842c2ae482e", + "type": "inject", + "z": "ae9e82a0a0e1eb89", + "name": "Definimos el nivel 8", + "props": [ + { + "p": "payload" + }, + { + "p": "nivel_hexagono", + "v": "8", + "vt": "num" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 130, + "y": 260, + "wires": [ + [ + "ee9152d1f76ccaac" + ] + ] + }, + { + "id": "41f3663b6be9dc20", + "type": "debug", + "z": "ae9e82a0a0e1eb89", + "name": "Resultado", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 580, + "y": 260, + "wires": [] + }, + { + "id": "ee9152d1f76ccaac", + "type": "function", + "z": "ae9e82a0a0e1eb89", + "name": "Realizamos los calculos", + "func": "// Node-RED Function Node Script\n// Genera tabla de hexágonos recorribles según velocidad y tiempo\n\n// Obtener parámetros de entrada\nconst nivel_hexagono = msg.nivel_hexagono || 8; // nivel H3 por defecto\nconst velocidad_min = msg.velocidad_min || 10; // km/h mínima\nconst velocidad_max = msg.velocidad_max || 30; // km/h máxima \nconst tiempo_minutos = msg.tiempo_minutos || 20; // minutos disponibles\n\nlet h3 = h3Js\n\n// Validar que tenemos las librerías necesarias\nif (typeof h3 === 'undefined') {\n node.error(\"H3 library not available\");\n return null;\n}\n\ntry {\n // Calcular longitud del lado del hexágono en metros\n const longitud_lado = h3.getHexagonEdgeLengthAvg(nivel_hexagono, 'm');\n\n // Convertir velocidades de km/h a m/s\n const velocidad_min_ms = (velocidad_min * 1000) / 3600;\n const velocidad_max_ms = (velocidad_max * 1000) / 3600;\n\n // Convertir tiempo de minutos a segundos\n const tiempo_segundos = tiempo_minutos * 60;\n\n // Calcular distancias máximas recorribles\n const distancia_min = velocidad_min_ms * tiempo_segundos;\n const distancia_max = velocidad_max_ms * tiempo_segundos;\n\n // Calcular número de hexágonos en cada dirección\n const hexagonos_min = Math.floor(distancia_min / longitud_lado);\n const hexagonos_max = Math.floor(distancia_max / longitud_lado);\n\n // Generar tabla de resultados\n const tabla_resultados = [];\n\n for (let num_hexagonos = hexagonos_min; num_hexagonos <= hexagonos_max; num_hexagonos++) {\n const distancia_recorrida = num_hexagonos * longitud_lado;\n const velocidad_requerida = (distancia_recorrida / tiempo_segundos) * 3.6; // Convertir a km/h\n\n tabla_resultados.push({\n num_hexagonos: num_hexagonos,\n distancia_metros: Math.round(distancia_recorrida),\n distancia_km: (distancia_recorrida / 1000).toFixed(2),\n velocidad_requerida_kmh: velocidad_requerida.toFixed(2),\n nivel_h3: nivel_hexagono,\n lado_hexagono_m: Math.round(longitud_lado)\n });\n }\n\n // Preparar mensaje de salida\n msg.payload = {\n parametros: {\n nivel_hexagono: nivel_hexagono,\n velocidad_min_kmh: velocidad_min,\n velocidad_max_kmh: velocidad_max,\n tiempo_minutos: tiempo_minutos,\n lado_hexagono_metros: Math.round(longitud_lado)\n },\n tabla: tabla_resultados,\n resumen: {\n hexagonos_minimos: hexagonos_min,\n hexagonos_maximos: hexagonos_max,\n rango_hexagonos: `${hexagonos_min} - ${hexagonos_max}`,\n distancia_minima_km: (hexagonos_min * longitud_lado / 1000).toFixed(2),\n distancia_maxima_km: (hexagonos_max * longitud_lado / 1000).toFixed(2)\n }\n };\n\n // Agregar información adicional para debugging\n msg.hexagonInfo = {\n nivel: nivel_hexagono,\n longitud_lado: longitud_lado\n };\n\n return msg;\n\n} catch (error) {\n node.error(\"Error processing hexagon data: \" + error.message);\n msg.error = error.message;\n return msg;\n}", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [ + { + "var": "h3Js", + "module": "h3-js" + } + ], + "x": 370, + "y": 260, + "wires": [ + [ + "41f3663b6be9dc20" + ] + ] + }, + { + "id": "0934bc707c9b611d", + "type": "comment", + "z": "ae9e82a0a0e1eb89", + "name": "Cálculo de la tabla de recorrido para la grua", + "info": "", + "x": 210, + "y": 180, + "wires": [] + }, + { + "id": "d801ff754db4d5e8", + "type": "inject", + "z": "ae9e82a0a0e1eb89", + "name": "Definimos el nivel 7", + "props": [ + { + "p": "payload" + }, + { + "p": "nivel_hexagono", + "v": "7", + "vt": "num" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 130, + "y": 220, + "wires": [ + [ + "ee9152d1f76ccaac" + ] + ] + }, + { + "id": "9ca4fbaf930ad8e1", + "type": "inject", + "z": "ae9e82a0a0e1eb89", + "name": "Definimos el nivel 9", + "props": [ + { + "p": "payload" + }, + { + "p": "nivel_hexagono", + "v": "9", + "vt": "num" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 130, + "y": 300, + "wires": [ + [ + "ee9152d1f76ccaac" + ] + ] + }, { "id": "ab02b07ce8843526", "type": "worldmap", @@ -35,8 +210,478 @@ "mapurl": "", "mapopt": "", "mapwms": false, - "x": 360, - "y": 140, + "x": 440, + "y": 280, + "wires": [] + }, + { + "id": "10f4dc8ae329f33e", + "type": "inject", + "z": "f511dd2230a153e7", + "name": "Mover mapa", + "props": [ + { + "p": "payload.command", + "v": "{\"lat\":\"40.40684\",\"lon\":\"-3.5711476\"}", + "vt": "json" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 290, + "y": 240, + "wires": [ + [ + "ab02b07ce8843526" + ] + ] + }, + { + "id": "5a0676c5ba4e943b", + "type": "inject", + "z": "f511dd2230a153e7", + "name": "Zoom", + "props": [ + { + "p": "payload.command", + "v": "{\"zoom\":15}", + "vt": "json" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 270, + "y": 280, + "wires": [ + [ + "ab02b07ce8843526" + ] + ] + }, + { + "id": "a94de8711099eff7", + "type": "inject", + "z": "f511dd2230a153e7", + "name": "House", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"name\":\"Casa\",\"icon\":\"home\",\"lat\":\"40.40412499978462\",\"lon\":\"-3.5643343002563848\"}", + "payloadType": "json", + "x": 270, + "y": 320, + "wires": [ + [ + "ab02b07ce8843526" + ] + ] + }, + { + "id": "33cf314d5cca7423", + "type": "worldmap in", + "z": "f511dd2230a153e7", + "name": "", + "path": "/worldmap", + "events": "connect,disconnect,point,layer,bounds,files,draw,other", + "x": 460, + "y": 760, + "wires": [ + [ + "bae0846569936702" + ] + ] + }, + { + "id": "bae0846569936702", + "type": "debug", + "z": "f511dd2230a153e7", + "name": "debug 1", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 610, + "y": 760, + "wires": [] + }, + { + "id": "acae736839e9942c", + "type": "postgresql", + "z": "f511dd2230a153e7", + "name": "Creación de la tabla de servicios_geo", + "query": "SELECT id_servicio,\n ST_AsGeoJSON(geom)::json AS geom\nFROM servicios_geo;", + "postgreSQLConfig": "748b5921246ec468", + "split": false, + "rowsPerMsg": 1, + "outputs": 1, + "x": 390, + "y": 60, + "wires": [ + [ + "e0f0d5d6ac05abf7" + ] + ] + }, + { + "id": "d9c3487d5a4cd9f2", + "type": "inject", + "z": "f511dd2230a153e7", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 110, + "y": 100, + "wires": [ + [ + "8115125206120309" + ] + ] + }, + { + "id": "e0f0d5d6ac05abf7", + "type": "debug", + "z": "f511dd2230a153e7", + "name": "debug 2", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 660, + "y": 120, + "wires": [] + }, + { + "id": "b34c1a218cd45651", + "type": "postgresql", + "z": "f511dd2230a153e7", + "name": "Consulta h3", + "query": "SELECT h3_lat_lng_to_cell(POINT('37.7749, -122.4194'), 9) AS h3_index;", + "postgreSQLConfig": "748b5921246ec468", + "split": false, + "rowsPerMsg": 1, + "outputs": 1, + "x": 290, + "y": 180, + "wires": [ + [ + "e0f0d5d6ac05abf7" + ] + ] + }, + { + "id": "b6be2c6139d939f0", + "type": "postgresql", + "z": "f511dd2230a153e7", + "name": "Consultar solo 500", + "query": "SELECT id_servicio,\n ST_AsGeoJSON(geom)::json AS geom\nFROM servicios_geo\nORDER BY id_servicio\nLIMIT 500", + "postgreSQLConfig": "748b5921246ec468", + "split": false, + "rowsPerMsg": 1, + "outputs": 1, + "x": 410, + "y": 120, + "wires": [ + [ + "e0f0d5d6ac05abf7" + ] + ] + }, + { + "id": "9fbe677b4f48e523", + "type": "postgresql", + "z": "f511dd2230a153e7", + "name": "Hablitar extension h3", + "query": "CREATE EXTENSION h3;", + "postgreSQLConfig": "748b5921246ec468", + "split": false, + "rowsPerMsg": 1, + "outputs": 1, + "x": 540, + "y": 200, + "wires": [ + [ + "e0f0d5d6ac05abf7" + ] + ] + }, + { + "id": "2af33006f75c5667", + "type": "postgresql", + "z": "f511dd2230a153e7", + "name": "Consulta h3", + "query": "SELECT h3_lat_lng_to_cell(\n point(ST_X(geom), ST_Y(geom)), -- lon, lat\n 7\n ) AS h3,\n COUNT(*) AS total\nFROM servicios_geo\nGROUP BY h3\nORDER BY total DESC;\n", + "postgreSQLConfig": "748b5921246ec468", + "split": false, + "rowsPerMsg": 1, + "outputs": 1, + "x": 310, + "y": 500, + "wires": [ + [ + "e0f0d5d6ac05abf7" + ] + ] + }, + { + "id": "8115125206120309", + "type": "postgresql", + "z": "f511dd2230a153e7", + "name": "Consulta h3", + "query": "SELECT h3_lat_lng_to_cell(\n point(ST_Y(geom), ST_X(geom)), -- lat, lon\n 7\n ) AS h3,\n COUNT(*) AS total\nFROM servicios_geo\nGROUP BY h3\nORDER BY total DESC;", + "postgreSQLConfig": "748b5921246ec468", + "split": false, + "rowsPerMsg": 1, + "outputs": 1, + "x": 310, + "y": 560, + "wires": [ + [ + "e0f0d5d6ac05abf7" + ] + ] + }, + { + "id": "1b3c9cf08f33d013", + "type": "postgresql", + "z": "f511dd2230a153e7", + "name": "Consulta h3 con geojson", + "query": "SELECT \n h3_lat_lng_to_cell(point(ST_Y(geom), ST_X(geom)), 9) AS h3,\n COUNT(*) AS total\nFROM servicios_geo\nGROUP BY h3;", + "postgreSQLConfig": "748b5921246ec468", + "split": false, + "rowsPerMsg": 1, + "outputs": 1, + "x": 350, + "y": 620, + "wires": [ + [ + "307dd85fbc2e3dc9" + ] + ] + }, + { + "id": "77fb49d72430c2b0", + "type": "inject", + "z": "f511dd2230a153e7", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 130, + "y": 620, + "wires": [ + [ + "1b3c9cf08f33d013" + ] + ] + }, + { + "id": "b660069d0a42ba72", + "type": "debug", + "z": "f511dd2230a153e7", + "name": "GEOJSON", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 710, + "y": 580, + "wires": [] + }, + { + "id": "fa52e6e1059e9acc", + "type": "worldmap", + "z": "f511dd2230a153e7", + "name": "", + "lat": "40.41341559118809", + "lon": "-3.695097056880839", + "zoom": "9", + "layer": "OSMC", + "cluster": "", + "maxage": "", + "usermenu": "show", + "layers": "show", + "panit": "false", + "panlock": "false", + "zoomlock": "false", + "hiderightclick": "false", + "coords": "none", + "showgrid": "false", + "showruler": "false", + "allowFileDrop": "false", + "path": "/worldmap", + "overlist": "", + "maplist": "OSMG,OSMC,EsriT", + "mapname": "", + "mapurl": "", + "mapopt": "", + "mapwms": false, + "x": 700, + "y": 620, + "wires": [] + }, + { + "id": "307dd85fbc2e3dc9", + "type": "function", + "z": "f511dd2230a153e7", + "name": "Pruebas", + "func": "let h3 = h3Js;\n\n// Validar que tenemos las librerías necesarias\nif (typeof h3 === 'undefined') {\n node.error(\"H3 library not available\");\n return null;\n}\n\nconst payload = msg.payload;\n\n// Obtener el total máximo para calcular el porcentaje\nconst maxTotal = Math.max(...payload.map(i => parseFloat(i.total)));\n\nconst features = [];\n\n// --- COLORES SEGÚN PORCENTAJE ---\nfunction getFillColor(pct) {\n if (pct <= 25) return \"#00ff00\"; // verde\n if (pct <= 50) return \"#ffff00\"; // amarillo\n if (pct <= 75) return \"#ffa500\"; // naranja\n return \"#ff0000\"; // rojo\n}\n\npayload.forEach(item => {\n const h3Index = item.h3;\n const total = parseFloat(item.total);\n\n try {\n const hexBoundary = h3.cellToBoundary(h3Index, true);\n const center = h3.cellToLatLng(h3Index);\n\n // Calcular porcentaje respecto al máximo\n const pct = maxTotal > 0 ? (total / maxTotal) * 100 : 0;\n\n const fillColor = getFillColor(pct);\n\n const feature = {\n \"type\": \"Feature\",\n \"properties\": {\n \"h3\": h3Index,\n \"total\": total,\n \"pct\": pct.toFixed(2),\n \"name\": `H3: ${h3Index} - ${pct.toFixed(1)}%`,\n \"resolution\": h3.getResolution(h3Index),\n \"lat\": center[0],\n \"lon\": center[1],\n\n // ESTILO\n \"fill\": fillColor,\n \"fill-opacity\": 0.5,\n \"stroke\": \"#000000\",\n \"stroke-width\": 1,\n \"stroke-opacity\": 0.9\n },\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [hexBoundary]\n }\n };\n\n features.push(feature);\n\n } catch (error) {\n node.warn(`Error procesando H3 index ${h3Index}: ${error}`);\n }\n});\n\n// FeatureCollection final\nmsg.payload = {\n \"type\": \"FeatureCollection\",\n \"features\": features,\n \"Layer\": \"H3 Layer\"\n};\n\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [ + { + "var": "h3Js", + "module": "h3-js" + } + ], + "x": 540, + "y": 620, + "wires": [ + [ + "fa52e6e1059e9acc", + "b660069d0a42ba72" + ] + ] + }, + { + "id": "27eb0e7ea2d102b6", + "type": "function", + "z": "f511dd2230a153e7", + "name": "Funciona en modo básico con colores", + "func": "let h3 = h3Js;\n\n// Validar que tenemos las librerías necesarias\nif (typeof h3 === 'undefined') {\n node.error(\"H3 library not available\");\n return null;\n}\n\nconst payload = msg.payload;\nconst features = [];\n\n// --- FUNCIONES PARA ASIGNAR COLORES SEGÚN EL TOTAL ---\nfunction getFillColor(total) {\n if (total <= 10) return \"#00ff00\"; // verde\n if (total <= 50) return \"#ffff00\"; // amarillo\n if (total <= 100) return \"#ffa500\"; // naranja\n return \"#ff0000\"; // rojo\n}\n\nfunction getStrokeColor(total) {\n return \"#000000\"; // negro (puedes cambiarlo o hacerlo dinámico también)\n}\n\n// Recorrer cada elemento del array\npayload.forEach(item => {\n const h3Index = item.h3;\n const total = parseInt(item.total);\n\n try {\n // Obtener los vértices del hexágono H3\n const hexBoundary = h3.cellToBoundary(h3Index, true);\n\n // Obtener el centro del hexágono\n const center = h3.cellToLatLng(h3Index);\n\n // Colores dinámicos\n const fillColor = getFillColor(total);\n const strokeColor = getStrokeColor(total);\n\n // Crear feature GeoJSON con estilo\n const feature = {\n \"type\": \"Feature\",\n \"properties\": {\n \"h3\": h3Index,\n \"total\": total,\n \"value\": total,\n \"name\": `H3: ${h3Index} - Total: ${total}`,\n \"resolution\": h3.getResolution(h3Index),\n \"lat\": center[0],\n \"lon\": center[1],\n\n // ---- ESTILO AÑADIDO ----\n \"fill\": fillColor,\n \"fill-opacity\": 0.5, // ajustable\n \"stroke\": strokeColor,\n \"stroke-width\": 1, // ajustable\n \"stroke-opacity\": 0.9 // ajustable\n },\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [hexBoundary]\n }\n };\n\n features.push(feature);\n\n } catch (error) {\n node.warn(`Error procesando H3 index ${h3Index}: ${error}`);\n }\n});\n\n// Crear el GeoJSON completo\nconst geojson = {\n \"type\": \"FeatureCollection\",\n \"features\": features,\n \"Layer\": \"H3 Layer\"\n};\n\nmsg.payload = geojson;\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [ + { + "var": "h3Js", + "module": "h3-js" + } + ], + "x": 210, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "e66c124d5be0c92e", + "type": "postgresql", + "z": "f511dd2230a153e7", + "name": "Consultar solo 500", + "query": "SELECT *\nFROM servicios_geo\nORDER BY id_servicio\nLIMIT 5", + "postgreSQLConfig": "748b5921246ec468", + "split": false, + "rowsPerMsg": 1, + "outputs": 1, + "x": 790, + "y": 340, + "wires": [ + [ + "95e107f031aca0fa" + ] + ] + }, + { + "id": "b76d4632ea730780", + "type": "inject", + "z": "f511dd2230a153e7", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 610, + "y": 340, + "wires": [ + [ + "e66c124d5be0c92e" + ] + ] + }, + { + "id": "95e107f031aca0fa", + "type": "debug", + "z": "f511dd2230a153e7", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 960, + "y": 340, "wires": [] } ] \ No newline at end of file