aboutsummaryrefslogtreecommitdiff
path: root/src/frontend
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2025-05-26 10:48:43 +0200
committerAriel Costas Guerrero <ariel@costas.dev>2025-05-26 10:48:43 +0200
commit5ced7f916d94e86e9a7ec164bee56f9a8e3a2a3a (patch)
treeb1ef5afa17b4a2f9fb2cbd683afc2fb6d905b5e1 /src/frontend
parent4637373b50636e78dc2c7b6f99be879edb4ff7dc (diff)
Replace Azure SWA with custom server
Diffstat (limited to 'src/frontend')
-rw-r--r--src/frontend/frontend.esproj22
-rw-r--r--src/frontend/index.html55
-rw-r--r--src/frontend/package-lock.json4083
-rw-r--r--src/frontend/package.json47
-rw-r--r--src/frontend/public/favicon.icobin0 -> 16958 bytes
-rw-r--r--src/frontend/public/logo-256.jpgbin0 -> 7492 bytes
-rw-r--r--src/frontend/public/logo-256.pngbin0 -> 37843 bytes
-rw-r--r--src/frontend/public/logo-512.jpgbin0 -> 16746 bytes
-rw-r--r--src/frontend/public/manifest.webmanifest84
-rw-r--r--src/frontend/public/map-pin-icon.pngbin0 -> 4033 bytes
-rw-r--r--src/frontend/public/screenshots/estimates-narrow.pngbin0 -> 219332 bytes
-rw-r--r--src/frontend/public/screenshots/estimates-wide.jpegbin0 -> 53529 bytes
-rw-r--r--src/frontend/public/screenshots/map-narrow.pngbin0 -> 2295265 bytes
-rw-r--r--src/frontend/public/screenshots/map-wide.jpegbin0 -> 336231 bytes
-rw-r--r--src/frontend/public/screenshots/stoplist-narrow.pngbin0 -> 297920 bytes
-rw-r--r--src/frontend/public/screenshots/stoplist-wide.jpegbin0 -> 102303 bytes
-rw-r--r--src/frontend/public/stops.json15037
-rw-r--r--src/frontend/public/sw.js51
-rw-r--r--src/frontend/src/AppContext.tsx234
-rw-r--r--src/frontend/src/ErrorBoundary.tsx46
-rw-r--r--src/frontend/src/Layout.css60
-rw-r--r--src/frontend/src/Layout.tsx55
-rw-r--r--src/frontend/src/components/GroupedTable.tsx74
-rw-r--r--src/frontend/src/components/LineIcon.css239
-rw-r--r--src/frontend/src/components/LineIcon.tsx17
-rw-r--r--src/frontend/src/components/RegularTable.tsx70
-rw-r--r--src/frontend/src/components/StopItem.css54
-rw-r--r--src/frontend/src/components/StopItem.tsx25
-rw-r--r--src/frontend/src/controls/LocateControl.ts67
-rw-r--r--src/frontend/src/data/StopDataProvider.ts160
-rw-r--r--src/frontend/src/main.tsx43
-rw-r--r--src/frontend/src/pages/Estimates.tsx99
-rw-r--r--src/frontend/src/pages/Map.tsx75
-rw-r--r--src/frontend/src/pages/Settings.tsx65
-rw-r--r--src/frontend/src/pages/StopList.tsx135
-rw-r--r--src/frontend/src/styles/Estimates.css105
-rw-r--r--src/frontend/src/styles/Map.css86
-rw-r--r--src/frontend/src/styles/Pages.css364
-rw-r--r--src/frontend/src/styles/Settings.css94
-rw-r--r--src/frontend/src/vite-env.d.ts1
-rw-r--r--src/frontend/tsconfig.json27
-rw-r--r--src/frontend/vite.config.ts26
42 files changed, 21600 insertions, 0 deletions
diff --git a/src/frontend/frontend.esproj b/src/frontend/frontend.esproj
new file mode 100644
index 0000000..1afb3fb
--- /dev/null
+++ b/src/frontend/frontend.esproj
@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.VisualStudio.JavaScript.SDK/1.0.2655793">
+ <PropertyGroup>
+ <StartupCommand>npm run dev</StartupCommand>
+ <BuildCommand>npm run build</BuildCommand>
+ <BuildOutputFolder>$(MSBuildProjectDirectory)\build</BuildOutputFolder>
+ </PropertyGroup>
+
+ <Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
+ <Exec Command="node --version" ContinueOnError="true">
+ <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
+ </Exec>
+ <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
+ <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take some time..." />
+ <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
+ </Target>
+
+ <Target Name="PublishRunVite" AfterTargets="ComputeFilesToPublish">
+ <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
+ <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
+ </Target>
+</Project>
+
diff --git a/src/frontend/index.html b/src/frontend/index.html
new file mode 100644
index 0000000..4812ce5
--- /dev/null
+++ b/src/frontend/index.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<html lang="es">
+
+<head>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <meta charset="UTF-8" />
+
+ <title>UrbanoVigo Web</title>
+
+ <link rel="icon" type="image/jpg" href="/logo-512.jpg" />
+ <link rel="icon" href="/favicon.ico" sizes="64x64" />
+ <link rel="apple-touch-icon" href="/logo-512.jpg" sizes="512x512" />
+ <meta name="theme-color" content="#007bff" />
+
+ <link rel="canonical" href="https://urbanovigo.costas.dev/" />
+
+ <meta name="description" content="Aplicación web para encontrar paradas y tiempos de llegada de los autobuses urbanos de Vigo, España." />
+ <meta name="keywords" content="Vigo, autobús, urbano, parada, tiempo, llegada, transporte, público, España" />
+ <meta name="author" content="Ariel Costas Guerrero" />
+
+ <meta property="og:title" content="UrbanoVigo Web" />
+ <meta property="og:type" content="website" />
+ <meta property="og:url" content="https://urbanovigo.costas.dev/" />
+ <meta property="og:image" content="https://urbanovigo.costas.dev/logo-512.jpg" />
+ <meta property="og:description" content="Aplicación web para encontrar paradas y tiempos de llegada de los autobuses urbanos de Vigo, España." />
+
+ <link rel="manifest" href="/manifest.webmanifest" />
+
+ <meta name="robots" content="noindex, nofollow" />
+ <meta name="googlebot" content="noindex, nofollow" />
+
+ <style>
+ body {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
+ </style>
+</head>
+
+<body>
+ <div id="root"></div>
+ <script type="module" src="/src/main.tsx"></script>
+
+ <script>
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.register('/sw.js');
+ navigator.serviceWorker.ready.then(registration => {
+ registration.update();
+ });
+ }
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json
new file mode 100644
index 0000000..b18eeb4
--- /dev/null
+++ b/src/frontend/package-lock.json
@@ -0,0 +1,4083 @@
+{
+ "name": "frontend",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "frontend",
+ "version": "0.0.0",
+ "dependencies": {
+ "@fontsource-variable/outfit": "^5.2.5",
+ "fuse.js": "^7.1.0",
+ "leaflet": "^1.9.4",
+ "leaflet.locatecontrol": "^0.84.2",
+ "leaflet.markercluster": "^1.5.3",
+ "lucide-react": "^0.510.0",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
+ "react-leaflet": "^5.0.0",
+ "react-leaflet-markercluster": "^5.0.0-rc.0",
+ "react-router": "^7.6.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.26.0",
+ "@types/leaflet": "^1.9.17",
+ "@types/node": "^22.15.17",
+ "@types/react": "^19.1.3",
+ "@types/react-dom": "^19.1.4",
+ "@vitejs/plugin-react-swc": "^3.9.0",
+ "eslint": "^9.26.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "globals": "^16.1.0",
+ "jiti": "^2.4.2",
+ "typescript": "^5.8.3",
+ "typescript-eslint": "^8.32.0",
+ "vite": "^6.3.5"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-linux-x64-gnu": "*"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
+ "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz",
+ "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz",
+ "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
+ "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz",
+ "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz",
+ "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz",
+ "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz",
+ "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
+ "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
+ "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz",
+ "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz",
+ "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz",
+ "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
+ "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
+ "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz",
+ "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz",
+ "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz",
+ "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz",
+ "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz",
+ "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz",
+ "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz",
+ "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz",
+ "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
+ "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz",
+ "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+ "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
+ "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz",
+ "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
+ "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.26.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz",
+ "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.2.8",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
+ "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.13.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@fontsource-variable/outfit": {
+ "version": "5.2.5",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/outfit/-/outfit-5.2.5.tgz",
+ "integrity": "sha512-MejrIp6Cbmd3u5AZtsot8kmhZiyQM5CATsdcBB2hktYIrv5CVekRSUmFDXL4FpF7K70IvFp2ZMfImm5VhnVf7Q==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
+ "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.11.2",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.2.tgz",
+ "integrity": "sha512-H9vwztj5OAqHg9GockCQC06k1natgcxWQSRpQcPJf6i5+MWBzfKkRtxGbjQf0X2ihii0ffLZCRGbYV2f2bjNCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.3",
+ "eventsource": "^3.0.2",
+ "express": "^5.0.1",
+ "express-rate-limit": "^7.5.0",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.23.8",
+ "zod-to-json-schema": "^3.24.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@react-leaflet/core": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz",
+ "integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==",
+ "license": "Hippocratic-2.1",
+ "peerDependencies": {
+ "leaflet": "^1.9.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz",
+ "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz",
+ "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz",
+ "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz",
+ "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz",
+ "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz",
+ "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz",
+ "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz",
+ "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz",
+ "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz",
+ "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz",
+ "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz",
+ "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz",
+ "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz",
+ "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz",
+ "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz",
+ "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz",
+ "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz",
+ "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz",
+ "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz",
+ "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@swc/core": {
+ "version": "1.11.24",
+ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.24.tgz",
+ "integrity": "sha512-MaQEIpfcEMzx3VWWopbofKJvaraqmL6HbLlw2bFZ7qYqYw3rkhM0cQVEgyzbHtTWwCwPMFZSC2DUbhlZgrMfLg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3",
+ "@swc/types": "^0.1.21"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/swc"
+ },
+ "optionalDependencies": {
+ "@swc/core-darwin-arm64": "1.11.24",
+ "@swc/core-darwin-x64": "1.11.24",
+ "@swc/core-linux-arm-gnueabihf": "1.11.24",
+ "@swc/core-linux-arm64-gnu": "1.11.24",
+ "@swc/core-linux-arm64-musl": "1.11.24",
+ "@swc/core-linux-x64-gnu": "1.11.24",
+ "@swc/core-linux-x64-musl": "1.11.24",
+ "@swc/core-win32-arm64-msvc": "1.11.24",
+ "@swc/core-win32-ia32-msvc": "1.11.24",
+ "@swc/core-win32-x64-msvc": "1.11.24"
+ },
+ "peerDependencies": {
+ "@swc/helpers": ">=0.5.17"
+ },
+ "peerDependenciesMeta": {
+ "@swc/helpers": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@swc/core-darwin-arm64": {
+ "version": "1.11.24",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.24.tgz",
+ "integrity": "sha512-dhtVj0PC1APOF4fl5qT2neGjRLgHAAYfiVP8poJelhzhB/318bO+QCFWAiimcDoyMgpCXOhTp757gnoJJrheWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-darwin-x64": {
+ "version": "1.11.24",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.24.tgz",
+ "integrity": "sha512-H/3cPs8uxcj2Fe3SoLlofN5JG6Ny5bl8DuZ6Yc2wr7gQFBmyBkbZEz+sPVgsID7IXuz7vTP95kMm1VL74SO5AQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm-gnueabihf": {
+ "version": "1.11.24",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.24.tgz",
+ "integrity": "sha512-PHJgWEpCsLo/NGj+A2lXZ2mgGjsr96ULNW3+T3Bj2KTc8XtMUkE8tmY2Da20ItZOvPNC/69KroU7edyo1Flfbw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-gnu": {
+ "version": "1.11.24",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.24.tgz",
+ "integrity": "sha512-C2FJb08+n5SD4CYWCTZx1uR88BN41ZieoHvI8A55hfVf2woT8+6ZiBzt74qW2g+ntZ535Jts5VwXAKdu41HpBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-musl": {
+ "version": "1.11.24",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.24.tgz",
+ "integrity": "sha512-ypXLIdszRo0re7PNNaXN0+2lD454G8l9LPK/rbfRXnhLWDBPURxzKlLlU/YGd2zP98wPcVooMmegRSNOKfvErw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-gnu": {
+ "version": "1.11.24",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.24.tgz",
+ "integrity": "sha512-IM7d+STVZD48zxcgo69L0yYptfhaaE9cMZ+9OoMxirNafhKKXwoZuufol1+alEFKc+Wbwp+aUPe/DeWC/Lh3dg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-musl": {
+ "version": "1.11.24",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.24.tgz",
+ "integrity": "sha512-DZByJaMVzSfjQKKQn3cqSeqwy6lpMaQDQQ4HPlch9FWtDx/dLcpdIhxssqZXcR2rhaQVIaRQsCqwV6orSDGAGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-arm64-msvc": {
+ "version": "1.11.24",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.24.tgz",
+ "integrity": "sha512-Q64Ytn23y9aVDKN5iryFi8mRgyHw3/kyjTjT4qFCa8AEb5sGUuSj//AUZ6c0J7hQKMHlg9do5Etvoe61V98/JQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-ia32-msvc": {
+ "version": "1.11.24",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.24.tgz",
+ "integrity": "sha512-9pKLIisE/Hh2vJhGIPvSoTK4uBSPxNVyXHmOrtdDot4E1FUUI74Vi8tFdlwNbaj8/vusVnb8xPXsxF1uB0VgiQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-x64-msvc": {
+ "version": "1.11.24",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.24.tgz",
+ "integrity": "sha512-sybnXtOsdB+XvzVFlBVGgRHLqp3yRpHK7CrmpuDKszhj/QhmsaZzY/GHSeALlMtLup13M0gqbcQvsTNlAHTg3w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@swc/types": {
+ "version": "0.1.21",
+ "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.21.tgz",
+ "integrity": "sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
+ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/leaflet": {
+ "version": "1.9.17",
+ "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.17.tgz",
+ "integrity": "sha512-IJ4K6t7I3Fh5qXbQ1uwL3CFVbCi6haW9+53oLWgdKlLP7EaS21byWFJxxqOx9y8I0AP0actXSJLVMbyvxhkUTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "22.15.17",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz",
+ "integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.1.3",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.3.tgz",
+ "integrity": "sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.1.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.4.tgz",
+ "integrity": "sha512-WxYAszDYgsMV31OVyoG4jbAgJI1Gw0Xq9V19zwhy6+hUUJlJIdZ3r/cbdmTqFv++SktQkZ/X+46yGFxp5XJBEg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.32.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.0.tgz",
+ "integrity": "sha512-/jU9ettcntkBFmWUzzGgsClEi2ZFiikMX5eEQsmxIAWMOn4H3D4rvHssstmAHGVvrYnaMqdWWWg0b5M6IN/MTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.32.0",
+ "@typescript-eslint/type-utils": "8.32.0",
+ "@typescript-eslint/utils": "8.32.0",
+ "@typescript-eslint/visitor-keys": "8.32.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.3.1",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.32.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.0.tgz",
+ "integrity": "sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.32.0",
+ "@typescript-eslint/types": "8.32.0",
+ "@typescript-eslint/typescript-estree": "8.32.0",
+ "@typescript-eslint/visitor-keys": "8.32.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.32.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.0.tgz",
+ "integrity": "sha512-jc/4IxGNedXkmG4mx4nJTILb6TMjL66D41vyeaPWvDUmeYQzF3lKtN15WsAeTr65ce4mPxwopPSo1yUUAWw0hQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.32.0",
+ "@typescript-eslint/visitor-keys": "8.32.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.32.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.0.tgz",
+ "integrity": "sha512-t2vouuYQKEKSLtJaa5bB4jHeha2HJczQ6E5IXPDPgIty9EqcJxpr1QHQ86YyIPwDwxvUmLfP2YADQ5ZY4qddZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "8.32.0",
+ "@typescript-eslint/utils": "8.32.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.32.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.0.tgz",
+ "integrity": "sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.32.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.0.tgz",
+ "integrity": "sha512-pU9VD7anSCOIoBFnhTGfOzlVFQIA1XXiQpH/CezqOBaDppRwTglJzCC6fUQGpfwey4T183NKhF1/mfatYmjRqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.32.0",
+ "@typescript-eslint/visitor-keys": "8.32.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.32.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.0.tgz",
+ "integrity": "sha512-8S9hXau6nQ/sYVtC3D6ISIDoJzS1NsCK+gluVhLN2YkBPX+/1wkwyUiDKnxRh15579WoOIyVWnoyIf3yGI9REw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.32.0",
+ "@typescript-eslint/types": "8.32.0",
+ "@typescript-eslint/typescript-estree": "8.32.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.32.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.0.tgz",
+ "integrity": "sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.32.0",
+ "eslint-visitor-keys": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react-swc": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.9.0.tgz",
+ "integrity": "sha512-jYFUSXhwMCYsh/aQTgSGLIN3Foz5wMbH9ahb0Zva//UzwZYbMiZd7oT3AU9jHT9DLswYDswsRwPU9jVF3yA48Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@swc/core": "^1.11.21"
+ },
+ "peerDependencies": {
+ "vite": "^4 || ^5 || ^6"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.14.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/body-parser": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.0",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.6.3",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.0",
+ "raw-body": "^3.0.0",
+ "type-is": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/content-disposition": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
+ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
+ "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.2",
+ "@esbuild/android-arm": "0.25.2",
+ "@esbuild/android-arm64": "0.25.2",
+ "@esbuild/android-x64": "0.25.2",
+ "@esbuild/darwin-arm64": "0.25.2",
+ "@esbuild/darwin-x64": "0.25.2",
+ "@esbuild/freebsd-arm64": "0.25.2",
+ "@esbuild/freebsd-x64": "0.25.2",
+ "@esbuild/linux-arm": "0.25.2",
+ "@esbuild/linux-arm64": "0.25.2",
+ "@esbuild/linux-ia32": "0.25.2",
+ "@esbuild/linux-loong64": "0.25.2",
+ "@esbuild/linux-mips64el": "0.25.2",
+ "@esbuild/linux-ppc64": "0.25.2",
+ "@esbuild/linux-riscv64": "0.25.2",
+ "@esbuild/linux-s390x": "0.25.2",
+ "@esbuild/linux-x64": "0.25.2",
+ "@esbuild/netbsd-arm64": "0.25.2",
+ "@esbuild/netbsd-x64": "0.25.2",
+ "@esbuild/openbsd-arm64": "0.25.2",
+ "@esbuild/openbsd-x64": "0.25.2",
+ "@esbuild/sunos-x64": "0.25.2",
+ "@esbuild/win32-arm64": "0.25.2",
+ "@esbuild/win32-ia32": "0.25.2",
+ "@esbuild/win32-x64": "0.25.2"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.26.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz",
+ "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.20.0",
+ "@eslint/config-helpers": "^0.2.1",
+ "@eslint/core": "^0.13.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.26.0",
+ "@eslint/plugin-kit": "^0.2.8",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@modelcontextprotocol/sdk": "^1.8.0",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.3.0",
+ "eslint-visitor-keys": "^4.2.0",
+ "espree": "^10.3.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "zod": "^3.24.2"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
+ "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.20",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz",
+ "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
+ "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+ "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.14.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz",
+ "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.0",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
+ "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": "^4.11 || 5 || ^5.0.0-beta.1"
+ }
+ },
+ "node_modules/express/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/fuse.js": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
+ "integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.1.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.1.0.tgz",
+ "integrity": "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
+ "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/leaflet": {
+ "version": "1.9.4",
+ "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
+ "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/leaflet.locatecontrol": {
+ "version": "0.84.2",
+ "resolved": "https://registry.npmjs.org/leaflet.locatecontrol/-/leaflet.locatecontrol-0.84.2.tgz",
+ "integrity": "sha512-Tv0S2bAhpFgZYyyfPgeVhb3hPr9CnlcP15EpMQd9m5vA+aALhM6key1ucfnnD7n09AEeNEmIFe71T4V18Kpu7g==",
+ "license": "MIT"
+ },
+ "node_modules/leaflet.markercluster": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz",
+ "integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "leaflet": "^1.3.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lucide-react": {
+ "version": "0.510.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.510.0.tgz",
+ "integrity": "sha512-p8SQRAMVh7NhsAIETokSqDrc5CHnDLbV29mMnzaXx+Vc/hnqQzwI2r0FMWCcoTXnbw2KEjy48xwpGdEL+ck06Q==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
+ "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkce-challenge": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
+ "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.3",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
+ "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.6.3",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
+ "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
+ "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.0"
+ }
+ },
+ "node_modules/react-leaflet": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz",
+ "integrity": "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==",
+ "license": "Hippocratic-2.1",
+ "dependencies": {
+ "@react-leaflet/core": "^3.0.0"
+ },
+ "peerDependencies": {
+ "leaflet": "^1.9.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ }
+ },
+ "node_modules/react-leaflet-markercluster": {
+ "version": "5.0.0-rc.0",
+ "resolved": "https://registry.npmjs.org/react-leaflet-markercluster/-/react-leaflet-markercluster-5.0.0-rc.0.tgz",
+ "integrity": "sha512-jWa4bPD5LfLV3Lid1RWgl+yKUuQtnqeYtJzzLb/fiRjvX+rtwzY8pMoUFuygqyxNrWxMTQlWKBHxkpI7Sxvu4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-leaflet/core": "^3.0.0",
+ "leaflet": "^1.9.4",
+ "leaflet.markercluster": "^1.5.3",
+ "react-leaflet": "^5.0.0"
+ },
+ "peerDependencies": {
+ "leaflet": "^1.9.4",
+ "leaflet.markercluster": "^1.5.3",
+ "react": "^19.0.0",
+ "react-leaflet": "^5.0.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz",
+ "integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz",
+ "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.7"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.40.0",
+ "@rollup/rollup-android-arm64": "4.40.0",
+ "@rollup/rollup-darwin-arm64": "4.40.0",
+ "@rollup/rollup-darwin-x64": "4.40.0",
+ "@rollup/rollup-freebsd-arm64": "4.40.0",
+ "@rollup/rollup-freebsd-x64": "4.40.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.40.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.40.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.40.0",
+ "@rollup/rollup-linux-arm64-musl": "4.40.0",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.40.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.40.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.40.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.40.0",
+ "@rollup/rollup-linux-x64-gnu": "4.40.0",
+ "@rollup/rollup-linux-x64-musl": "4.40.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.40.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.40.0",
+ "@rollup/rollup-win32-x64-msvc": "4.40.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.5",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "mime-types": "^3.0.1",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
+ "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.4.4",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
+ "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.32.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.0.tgz",
+ "integrity": "sha512-UMq2kxdXCzinFFPsXc9o2ozIpYCCOiEC46MG3yEh5Vipq6BO27otTtEBZA1fQ66DulEUgE97ucQ/3YY66CPg0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.32.0",
+ "@typescript-eslint/parser": "8.32.0",
+ "@typescript-eslint/utils": "8.32.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.3.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
+ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/fdir": {
+ "version": "6.4.4",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
+ "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.24.4",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz",
+ "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.24.5",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz",
+ "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==",
+ "dev": true,
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.24.1"
+ }
+ }
+ }
+}
diff --git a/src/frontend/package.json b/src/frontend/package.json
new file mode 100644
index 0000000..546b9cf
--- /dev/null
+++ b/src/frontend/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "frontend",
+ "private": true,
+ "version": "0.0.0",
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@fontsource-variable/outfit": "^5.2.5",
+ "fuse.js": "^7.1.0",
+ "leaflet": "^1.9.4",
+ "leaflet.locatecontrol": "^0.84.2",
+ "leaflet.markercluster": "^1.5.3",
+ "lucide-react": "^0.510.0",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
+ "react-leaflet": "^5.0.0",
+ "react-leaflet-markercluster": "^5.0.0-rc.0",
+ "react-router": "^7.6.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.26.0",
+ "@types/leaflet": "^1.9.17",
+ "@types/node": "^22.15.17",
+ "@types/react": "^19.1.3",
+ "@types/react-dom": "^19.1.4",
+ "@vitejs/plugin-react-swc": "^3.9.0",
+ "eslint": "^9.26.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "globals": "^16.1.0",
+ "jiti": "^2.4.2",
+ "typescript": "^5.8.3",
+ "typescript-eslint": "^8.32.0",
+ "vite": "^6.3.5"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-linux-x64-gnu": "*"
+ }
+}
diff --git a/src/frontend/public/favicon.ico b/src/frontend/public/favicon.ico
new file mode 100644
index 0000000..b81c323
--- /dev/null
+++ b/src/frontend/public/favicon.ico
Binary files differ
diff --git a/src/frontend/public/logo-256.jpg b/src/frontend/public/logo-256.jpg
new file mode 100644
index 0000000..c823056
--- /dev/null
+++ b/src/frontend/public/logo-256.jpg
Binary files differ
diff --git a/src/frontend/public/logo-256.png b/src/frontend/public/logo-256.png
new file mode 100644
index 0000000..a1d6c25
--- /dev/null
+++ b/src/frontend/public/logo-256.png
Binary files differ
diff --git a/src/frontend/public/logo-512.jpg b/src/frontend/public/logo-512.jpg
new file mode 100644
index 0000000..cf45e80
--- /dev/null
+++ b/src/frontend/public/logo-512.jpg
Binary files differ
diff --git a/src/frontend/public/manifest.webmanifest b/src/frontend/public/manifest.webmanifest
new file mode 100644
index 0000000..59fbab1
--- /dev/null
+++ b/src/frontend/public/manifest.webmanifest
@@ -0,0 +1,84 @@
+{
+ "$schema": "https://raw.githubusercontent.com/SchemaStore/schemastore/refs/heads/master/src/schemas/json/web-manifest.json",
+ "id": "https://busurbano.costas.dev/",
+ "name": "UrbanoVigo Web",
+ "description": "Aplicación web para encontrar paradas y tiempos de llegada de los autobuses urbanos de Vigo, España.",
+ "short_name": "UrbanoVigo",
+ "start_url": "/",
+ "display": "standalone",
+ "orientation": "portrait-primary",
+ "lang": "es",
+ "background_color": "#ffffff",
+ "theme_color": "#007bff",
+ "icons": [
+ {
+ "src": "/logo-512.jpg",
+ "sizes": "512x512",
+ "type": "image/jpg",
+ "purpose": "any maskable"
+ },
+ {
+ "src": "/logo-256.jpg",
+ "sizes": "256x256",
+ "type": "image/jpg",
+ "purpose": "any maskable"
+ },
+ {
+ "src": "/logo-256.png",
+ "sizes": "256x256",
+ "type": "image/png",
+ "purpose": "any maskable"
+ },
+ {
+ "src": "/favicon.ico",
+ "sizes": "64x64",
+ "type": "image/x-icon",
+ "purpose": "any maskable"
+ }
+ ],
+ "screenshots": [
+ {
+ "src": "/screenshots/stoplist-narrow.png",
+ "sizes": "1440x2960",
+ "type": "image/png",
+ "form_factor": "narrow",
+ "label": "Lista de paradas"
+ },
+ {
+ "src": "/screenshots/map-narrow.png",
+ "sizes": "1440x2960",
+ "type": "image/png",
+ "form_factor": "narrow",
+ "label": "Mapa de paradas"
+ },
+ {
+ "src": "/screenshots/estimates-narrow.png",
+ "sizes": "1440x2960",
+ "type": "image/png",
+ "form_factor": "narrow",
+ "label": "Estimaciones de llegada a parada"
+ },
+
+ {
+ "src": "/screenshots/stoplist-wide.jpeg",
+ "sizes": "1788x891",
+ "type": "image/jpeg",
+ "form_factor": "wide",
+ "label": "Lista de paradas"
+ },
+ {
+ "src": "/screenshots/map-wide.jpeg",
+ "sizes": "1788x891",
+ "type": "image/jpeg",
+ "form_factor": "wide",
+ "label": "Mapa de paradas"
+ },
+ {
+ "src": "/screenshots/estimates-wide.jpeg",
+ "sizes": "1788x891",
+ "type": "image/jpeg",
+ "form_factor": "wide",
+ "label": "Estimaciones de llegada a parada"
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/frontend/public/map-pin-icon.png b/src/frontend/public/map-pin-icon.png
new file mode 100644
index 0000000..0015b64
--- /dev/null
+++ b/src/frontend/public/map-pin-icon.png
Binary files differ
diff --git a/src/frontend/public/screenshots/estimates-narrow.png b/src/frontend/public/screenshots/estimates-narrow.png
new file mode 100644
index 0000000..0337442
--- /dev/null
+++ b/src/frontend/public/screenshots/estimates-narrow.png
Binary files differ
diff --git a/src/frontend/public/screenshots/estimates-wide.jpeg b/src/frontend/public/screenshots/estimates-wide.jpeg
new file mode 100644
index 0000000..e81f094
--- /dev/null
+++ b/src/frontend/public/screenshots/estimates-wide.jpeg
Binary files differ
diff --git a/src/frontend/public/screenshots/map-narrow.png b/src/frontend/public/screenshots/map-narrow.png
new file mode 100644
index 0000000..14199c4
--- /dev/null
+++ b/src/frontend/public/screenshots/map-narrow.png
Binary files differ
diff --git a/src/frontend/public/screenshots/map-wide.jpeg b/src/frontend/public/screenshots/map-wide.jpeg
new file mode 100644
index 0000000..6d3ca64
--- /dev/null
+++ b/src/frontend/public/screenshots/map-wide.jpeg
Binary files differ
diff --git a/src/frontend/public/screenshots/stoplist-narrow.png b/src/frontend/public/screenshots/stoplist-narrow.png
new file mode 100644
index 0000000..c1d9f25
--- /dev/null
+++ b/src/frontend/public/screenshots/stoplist-narrow.png
Binary files differ
diff --git a/src/frontend/public/screenshots/stoplist-wide.jpeg b/src/frontend/public/screenshots/stoplist-wide.jpeg
new file mode 100644
index 0000000..264e1a7
--- /dev/null
+++ b/src/frontend/public/screenshots/stoplist-wide.jpeg
Binary files differ
diff --git a/src/frontend/public/stops.json b/src/frontend/public/stops.json
new file mode 100644
index 0000000..f64dff8
--- /dev/null
+++ b/src/frontend/public/stops.json
@@ -0,0 +1,15037 @@
+[
+ {
+ "stopId": 20,
+ "name": {
+ "original": "Rúa do Abade Juan de Bastos (fronte Asociación Veciños)"
+ },
+ "latitude": 42.187593499,
+ "longitude": -8.741246641,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 40,
+ "name": {
+ "original": "Rúa do Abade Juan de Bastos (cruce Baixada da Moo)"
+ },
+ "latitude": 42.192126677,
+ "longitude": -8.72901589,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 50,
+ "name": {
+ "original": "Rúa do Abade Juan de Bastos 24"
+ },
+ "latitude": 42.19287042,
+ "longitude": -8.727513924,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 70,
+ "name": {
+ "original": "Rúa da Lagoa (cruce Camiño do Casmarcelo)"
+ },
+ "latitude": 42.20020175,
+ "longitude": -8.700621608,
+ "lines": [
+ "18A",
+ "18B"
+ ]
+ },
+ {
+ "stopId": 80,
+ "name": {
+ "original": "Rúa da Lagoa 46"
+ },
+ "latitude": 42.200132216,
+ "longitude": -8.700535777,
+ "lines": [
+ "18A",
+ "18B"
+ ]
+ },
+ {
+ "stopId": 90,
+ "name": {
+ "original": "Aeroporto de Peinador"
+ },
+ "latitude": 42.225956918,
+ "longitude": -8.63286469,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 100,
+ "name": {
+ "original": "Avda. do Alcalde Lavadores 125"
+ },
+ "latitude": 42.219008975,
+ "longitude": -8.69606935,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 110,
+ "name": {
+ "original": "Avda. do Alcalde Lavadores 171"
+ },
+ "latitude": 42.215074591,
+ "longitude": -8.696738405,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 120,
+ "name": {
+ "original": "Avda. do Alcalde Lavadores 8"
+ },
+ "latitude": 42.223288295,
+ "longitude": -8.700954873,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 130,
+ "name": {
+ "original": "Avda. do Alcalde Lavadores 102"
+ },
+ "latitude": 42.219001694,
+ "longitude": -8.696198267,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 140,
+ "name": {
+ "original": "Avda. do Alcalde Lavadores 29"
+ },
+ "latitude": 42.223444913,
+ "longitude": -8.700801996,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 150,
+ "name": {
+ "original": "Avda. do Alcalde Lavadores 48"
+ },
+ "latitude": 42.222636676,
+ "longitude": -8.697201413,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 160,
+ "name": {
+ "original": "Avda. do Alcalde Lavadores 67"
+ },
+ "latitude": 42.222830286,
+ "longitude": -8.697231476,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 170,
+ "name": {
+ "original": "Avda. do Alcalde Lavadores 152"
+ },
+ "latitude": 42.215084316,
+ "longitude": -8.696854931,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 180,
+ "name": {
+ "original": "Estrada de Valadares 451"
+ },
+ "latitude": 42.166144986,
+ "longitude": -8.720162371,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 190,
+ "name": {
+ "original": "Rúa de Ángel de Lema 58"
+ },
+ "latitude": 42.250539537,
+ "longitude": -8.685179363,
+ "lines": [
+ "C3i",
+ "10"
+ ]
+ },
+ {
+ "stopId": 195,
+ "name": {
+ "original": "Rúa de Ángel de Lema 247"
+ },
+ "latitude": 42.256624708,
+ "longitude": -8.677490797,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 200,
+ "name": {
+ "original": "Rúa de Ángel de Lema 100"
+ },
+ "latitude": 42.252115803,
+ "longitude": -8.683374373,
+ "lines": [
+ "C3i",
+ "10"
+ ]
+ },
+ {
+ "stopId": 210,
+ "name": {
+ "original": "Rúa de Ángel de Lema 140"
+ },
+ "latitude": 42.255798748,
+ "longitude": -8.678507526,
+ "lines": [
+ "C3i",
+ "10"
+ ]
+ },
+ {
+ "stopId": 220,
+ "name": {
+ "original": "Rúa de Ángel de Lema 163"
+ },
+ "latitude": 42.252694363,
+ "longitude": -8.68302903,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 230,
+ "name": {
+ "original": "Rúa de Ángel de Lema 14"
+ },
+ "latitude": 42.248041601,
+ "longitude": -8.691024475,
+ "lines": [
+ "C3i",
+ "10"
+ ]
+ },
+ {
+ "stopId": 240,
+ "name": {
+ "original": "Rúa de Ángel de Lema 19"
+ },
+ "latitude": 42.247513476,
+ "longitude": -8.691874301,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 250,
+ "name": {
+ "original": "Rúa de Ángel de Lema 221"
+ },
+ "latitude": 42.255252085,
+ "longitude": -8.679480662,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 260,
+ "name": {
+ "original": "Rúa de Ángel de Lema 91"
+ },
+ "latitude": 42.250421216,
+ "longitude": -8.685464716,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 270,
+ "name": {
+ "original": "Rúa de Desiderio Pernas Arquitecto 1"
+ },
+ "latitude": 42.18920151,
+ "longitude": -8.810340862,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 280,
+ "name": {
+ "original": "Rúa do Arquitecto Antonio Cominges 38"
+ },
+ "latitude": 42.189490674,
+ "longitude": -8.808107114,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 290,
+ "name": {
+ "original": "Rúa do Arquitecto Gómez Román 37"
+ },
+ "latitude": 42.190149471,
+ "longitude": -8.803788225,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 310,
+ "name": {
+ "original": "Rúa do Arquitecto Antonio Cominges 4"
+ },
+ "latitude": 42.190850463,
+ "longitude": -8.80358845,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 320,
+ "name": {
+ "original": "Rúa do Arquitecto Antonio Cominges 70"
+ },
+ "latitude": 42.189221331,
+ "longitude": -8.811730246,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 330,
+ "name": {
+ "original": "Rúa do Arquitecto Antonio Cominges 90"
+ },
+ "latitude": 42.187213169,
+ "longitude": -8.813069201,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 340,
+ "name": {
+ "original": "Rúa de Aragón 116"
+ },
+ "latitude": 42.238036494,
+ "longitude": -8.700921187,
+ "lines": [
+ "4A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 350,
+ "name": {
+ "original": "Rúa de Aragón 162"
+ },
+ "latitude": 42.240488915,
+ "longitude": -8.700357923,
+ "lines": [
+ "4A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 360,
+ "name": {
+ "original": "Rúa de Aragón 193"
+ },
+ "latitude": 42.24013184,
+ "longitude": -8.700947033,
+ "lines": [
+ "4A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 370,
+ "name": {
+ "original": "Rúa de Aragón 212"
+ },
+ "latitude": 42.242101304,
+ "longitude": -8.698394546,
+ "lines": [
+ "4A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 380,
+ "name": {
+ "original": "Rúa de Aragón 221"
+ },
+ "latitude": 42.242091376,
+ "longitude": -8.698668131,
+ "lines": [
+ "4A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 390,
+ "name": {
+ "original": "Rúa de Aragón 26"
+ },
+ "latitude": 42.233174046,
+ "longitude": -8.702380309,
+ "lines": [
+ "4A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 400,
+ "name": {
+ "original": "Rúa de Aragón 91"
+ },
+ "latitude": 42.235598195,
+ "longitude": -8.701426538,
+ "lines": [
+ "4A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 410,
+ "name": {
+ "original": "Rúa de Aragón 82"
+ },
+ "latitude": 42.235524387,
+ "longitude": -8.701248417,
+ "lines": [
+ "4A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 420,
+ "name": {
+ "original": "Rúa de Aragón 147"
+ },
+ "latitude": 42.238092485,
+ "longitude": -8.701156245,
+ "lines": [
+ "4A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 430,
+ "name": {
+ "original": "Rúa do Areal (Aduana)"
+ },
+ "latitude": 42.239341996,
+ "longitude": -8.720234413,
+ "lines": [
+ "A",
+ "6",
+ "9B",
+ "18A",
+ "24",
+ "28",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 530,
+ "name": {
+ "original": "Avda. de Ricardo Mella (Estación Coruxo)"
+ },
+ "latitude": 42.193562859,
+ "longitude": -8.78173994,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 540,
+ "name": {
+ "original": "Avda. de Ricardo Mella (fronte 223)"
+ },
+ "latitude": 42.189424424,
+ "longitude": -8.790733064,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 560,
+ "name": {
+ "original": "Avda. de Ricardo Mella 518"
+ },
+ "latitude": 42.181015915,
+ "longitude": -8.807696921,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 570,
+ "name": {
+ "original": "Avda. de Ricardo Mella 250"
+ },
+ "latitude": 42.195225102,
+ "longitude": -8.775226375,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 572,
+ "name": {
+ "original": "Estrada de Madrid 210"
+ },
+ "latitude": 42.214058797,
+ "longitude": -8.672946954,
+ "lines": [
+ "12B",
+ "15B",
+ "15C",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 580,
+ "name": {
+ "original": "Avda. de Ricardo Mella 135"
+ },
+ "latitude": 42.195766012,
+ "longitude": -8.773648966,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 600,
+ "name": {
+ "original": "Avda. de Ricardo Mella 273"
+ },
+ "latitude": 42.189927171,
+ "longitude": -8.800634184,
+ "lines": [
+ "C3d",
+ "10",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 620,
+ "name": {
+ "original": "Avda. de Ricado Mella 165"
+ },
+ "latitude": 42.1935678,
+ "longitude": -8.781529566,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 630,
+ "name": {
+ "original": "Avda. de Ricardo Mella 223"
+ },
+ "latitude": 42.189304527,
+ "longitude": -8.79068363,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 650,
+ "name": {
+ "original": "Avda. de Ricardo Mella 289"
+ },
+ "latitude": 42.181065441,
+ "longitude": -8.807509871,
+ "lines": [
+ "C3d",
+ "10",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 660,
+ "name": {
+ "original": "Avda. do Alcalde Portanet 34"
+ },
+ "latitude": 42.211494566,
+ "longitude": -8.736022397,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 680,
+ "name": {
+ "original": "Avda. do Aeroporto (Aeroclub)"
+ },
+ "latitude": 42.229005723,
+ "longitude": -8.634356866,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 690,
+ "name": {
+ "original": "Avda. do Aeroporto 656"
+ },
+ "latitude": 42.233064093,
+ "longitude": -8.642742935,
+ "lines": [
+ "A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 700,
+ "name": {
+ "original": "Avda. do Aeroporto (Colexio)"
+ },
+ "latitude": 42.228674047,
+ "longitude": -8.633340309,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 710,
+ "name": {
+ "original": "Rúa de Aragón (Instituto)"
+ },
+ "latitude": 42.232478958,
+ "longitude": -8.701988706,
+ "lines": [
+ "A",
+ "4A",
+ "9B",
+ "24",
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 720,
+ "name": {
+ "original": "Avda. do Aeroporto 215"
+ },
+ "latitude": 42.235739016,
+ "longitude": -8.684254232,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 730,
+ "name": {
+ "original": "Avda. do Aeroporto 130"
+ },
+ "latitude": 42.231109162,
+ "longitude": -8.690501398,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 740,
+ "name": {
+ "original": "Avda. do Aeroporto 181"
+ },
+ "latitude": 42.233560754,
+ "longitude": -8.686937524,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 750,
+ "name": {
+ "original": "Avda. do Aeroporto 184"
+ },
+ "latitude": 42.233103986,
+ "longitude": -8.68716283,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 760,
+ "name": {
+ "original": "Avda. do Aeroporto 240"
+ },
+ "latitude": 42.236775611,
+ "longitude": -8.683736566,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 770,
+ "name": {
+ "original": "Avda. do Aeroporto 273"
+ },
+ "latitude": 42.238939528,
+ "longitude": -8.681422497,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 780,
+ "name": {
+ "original": "Avda. do Aeroporto 298"
+ },
+ "latitude": 42.238554288,
+ "longitude": -8.680663432,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 790,
+ "name": {
+ "original": "Avda. do Aeroporto 325"
+ },
+ "latitude": 42.237426811,
+ "longitude": -8.675474476,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 800,
+ "name": {
+ "original": "Avda. do Aeroporto 328"
+ },
+ "latitude": 42.237801674,
+ "longitude": -8.676524783,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 810,
+ "name": {
+ "original": "Avda. do Aeroporto 350"
+ },
+ "latitude": 42.235521261,
+ "longitude": -8.67465521,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 820,
+ "name": {
+ "original": "Avda. do Aeroporto 377"
+ },
+ "latitude": 42.234766626,
+ "longitude": -8.671305131,
+ "lines": [
+ "A",
+ "9B"
+ ]
+ },
+ {
+ "stopId": 830,
+ "name": {
+ "original": "Avda. do Aeroporto 378"
+ },
+ "latitude": 42.234673289,
+ "longitude": -8.671348046,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 840,
+ "name": {
+ "original": "Avda. do Aeroporto 43"
+ },
+ "latitude": 42.234904325,
+ "longitude": -8.699245802,
+ "lines": [
+ "A",
+ "4A",
+ "9B",
+ "24",
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 850,
+ "name": {
+ "original": "Avda. do Aeroporto 423"
+ },
+ "latitude": 42.23630176,
+ "longitude": -8.665791599,
+ "lines": [
+ "A",
+ "9B"
+ ]
+ },
+ {
+ "stopId": 860,
+ "name": {
+ "original": "Avda. do Aeroporto 446"
+ },
+ "latitude": 42.235612667,
+ "longitude": -8.666529207,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 870,
+ "name": {
+ "original": "Avda. do Aeroporto 447"
+ },
+ "latitude": 42.23543058,
+ "longitude": -8.66197943,
+ "lines": [
+ "A",
+ "9B"
+ ]
+ },
+ {
+ "stopId": 880,
+ "name": {
+ "original": "Avda. do Aeroporto 484"
+ },
+ "latitude": 42.23544051,
+ "longitude": -8.662354939,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 890,
+ "name": {
+ "original": "Avda. do Aeroporto 491"
+ },
+ "latitude": 42.232066419,
+ "longitude": -8.653842977,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 900,
+ "name": {
+ "original": "Avda. do Aeroporto 531"
+ },
+ "latitude": 42.233527998,
+ "longitude": -8.648237616,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 910,
+ "name": {
+ "original": "Avda. do Aeroporto 54"
+ },
+ "latitude": 42.234679919,
+ "longitude": -8.699623994,
+ "lines": [
+ "A",
+ "4A",
+ "9B",
+ "24",
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 920,
+ "name": {
+ "original": "Avda. do Aeroporto (cruce Camiño das Cereixeiras)"
+ },
+ "latitude": 42.233499069,
+ "longitude": -8.643325214,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 930,
+ "name": {
+ "original": "Avda. do Aeroporto 570"
+ },
+ "latitude": 42.231979036,
+ "longitude": -8.65372496,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 940,
+ "name": {
+ "original": "Avda. do Aeroporto 605"
+ },
+ "latitude": 42.230493878,
+ "longitude": -8.638023273,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 950,
+ "name": {
+ "original": "Avda. do Aeroporto 614"
+ },
+ "latitude": 42.233626818,
+ "longitude": -8.647811163,
+ "lines": [
+ "A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 960,
+ "name": {
+ "original": "Avda. do Aeroporto 686"
+ },
+ "latitude": 42.230918888,
+ "longitude": -8.638532893,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 970,
+ "name": {
+ "original": "Avda. do Aeroporto 91"
+ },
+ "latitude": 42.232787318,
+ "longitude": -8.693473285,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 980,
+ "name": {
+ "original": "Avda. da Atlántida 99"
+ },
+ "latitude": 42.221170087,
+ "longitude": -8.763656977,
+ "lines": [
+ "10",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 990,
+ "name": {
+ "original": "Avda. da Atlántida (fronte 148)"
+ },
+ "latitude": 42.222451366,
+ "longitude": -8.769134894,
+ "lines": [
+ "10",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 1000,
+ "name": {
+ "original": "Avda. da Atlántida 109"
+ },
+ "latitude": 42.221220508,
+ "longitude": -8.767194468,
+ "lines": [
+ "10",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 1010,
+ "name": {
+ "original": "Avda. da Atlántida 136"
+ },
+ "latitude": 42.221479642,
+ "longitude": -8.767482698,
+ "lines": [
+ "10",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 1020,
+ "name": {
+ "original": "Avda. da Atlántida 150"
+ },
+ "latitude": 42.222764778,
+ "longitude": -8.769405842,
+ "lines": [
+ "10",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 1030,
+ "name": {
+ "original": "Avda. da Atlántida 25"
+ },
+ "latitude": 42.223219677,
+ "longitude": -8.754753277,
+ "lines": [
+ "10",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 1040,
+ "name": {
+ "original": "Avda. da Atlántida 32"
+ },
+ "latitude": 42.223237503,
+ "longitude": -8.755707801,
+ "lines": [
+ "10",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 1050,
+ "name": {
+ "original": "Avda. da Atlántida 71"
+ },
+ "latitude": 42.221875354,
+ "longitude": -8.760935381,
+ "lines": [
+ "10",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 1060,
+ "name": {
+ "original": "Avda. da Atlántida 84"
+ },
+ "latitude": 42.221789505,
+ "longitude": -8.759905458,
+ "lines": [
+ "10",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 1070,
+ "name": {
+ "original": "Avda. da Atlántida 114"
+ },
+ "latitude": 42.221148357,
+ "longitude": -8.764660969,
+ "lines": [
+ "10",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 1110,
+ "name": {
+ "original": "Praza Ribeira do Berbés"
+ },
+ "latitude": 42.237821273,
+ "longitude": -8.729666379,
+ "lines": [
+ "A",
+ "5B",
+ "6"
+ ]
+ },
+ {
+ "stopId": 1120,
+ "name": {
+ "original": "Avda. de Beiramar (fronte Casa do Mar)"
+ },
+ "latitude": 42.23416729,
+ "longitude": -8.733331094,
+ "lines": [
+ "6",
+ "9B",
+ "15B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 1130,
+ "name": {
+ "original": "Avda. de Beiramar (Peiraos auxiliares)"
+ },
+ "latitude": 42.231238831,
+ "longitude": -8.735255297,
+ "lines": [
+ "6",
+ "9B",
+ "15B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 1140,
+ "name": {
+ "original": "Avda. de Beiramar (Freire)"
+ },
+ "latitude": 42.225068475,
+ "longitude": -8.74774586,
+ "lines": [
+ "6",
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 1150,
+ "name": {
+ "original": "Rúa da Ribeira do Berbés"
+ },
+ "latitude": 42.237384264,
+ "longitude": -8.729603006,
+ "lines": [
+ "C1",
+ "C3d",
+ "A",
+ "5A",
+ "9B",
+ "10",
+ "15B",
+ "15C",
+ "28",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 1160,
+ "name": {
+ "original": "Avda. de Beiramar (Sto. Domingo)"
+ },
+ "latitude": 42.225759663,
+ "longitude": -8.743239749,
+ "lines": [
+ "6",
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 1200,
+ "name": {
+ "original": "Avda. de Beiramar 51"
+ },
+ "latitude": 42.234233798,
+ "longitude": -8.73312316,
+ "lines": [
+ "10",
+ "15B"
+ ]
+ },
+ {
+ "stopId": 1210,
+ "name": {
+ "original": "Avda. de Beiramar 61"
+ },
+ "latitude": 42.230811976,
+ "longitude": -8.735364934,
+ "lines": [
+ "10",
+ "15B"
+ ]
+ },
+ {
+ "stopId": 1220,
+ "name": {
+ "original": "Avda. de Buenos Aires 46"
+ },
+ "latitude": 42.247097055,
+ "longitude": -8.693109251,
+ "lines": [
+ "5A",
+ "10",
+ "31",
+ "U2",
+ "H2",
+ "H3",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 1230,
+ "name": {
+ "original": "Avda. de Buenos Aires 49"
+ },
+ "latitude": 42.247251925,
+ "longitude": -8.693122662,
+ "lines": [
+ "5B",
+ "10",
+ "N1",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 1240,
+ "name": {
+ "original": "Avda. de Buenos Aires 8"
+ },
+ "latitude": 42.249128205,
+ "longitude": -8.69514773,
+ "lines": [
+ "5A",
+ "10",
+ "31",
+ "U2",
+ "H2",
+ "H3",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 1250,
+ "name": {
+ "original": "Avda. de Castelao 16"
+ },
+ "latitude": 42.219730396,
+ "longitude": -8.737456513,
+ "lines": [
+ "C3d",
+ "4A",
+ "4C",
+ "5B",
+ "10",
+ "12A",
+ "13",
+ "15A",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 1260,
+ "name": {
+ "original": "Avda. de Castelao 21"
+ },
+ "latitude": 42.219775977,
+ "longitude": -8.736255523,
+ "lines": [
+ "C3i",
+ "4A",
+ "4C",
+ "5B",
+ "10",
+ "11",
+ "12A",
+ "15A",
+ "N1",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 1270,
+ "name": {
+ "original": "Avda. de Castelao 50"
+ },
+ "latitude": 42.218704937,
+ "longitude": -8.74254446,
+ "lines": [
+ "C3d",
+ "4A",
+ "4C",
+ "5B",
+ "10",
+ "12A",
+ "13",
+ "15A",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 1280,
+ "name": {
+ "original": "Avda. de Castelao 41"
+ },
+ "latitude": 42.218523315,
+ "longitude": -8.74223465,
+ "lines": [
+ "C3i",
+ "4A",
+ "4C",
+ "10",
+ "11",
+ "12A",
+ "15A",
+ "N1",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 1290,
+ "name": {
+ "original": "Avda. de Castelao 54"
+ },
+ "latitude": 42.218158263,
+ "longitude": -8.745797727,
+ "lines": [
+ "C3d",
+ "4A",
+ "4C",
+ "5B",
+ "10",
+ "12A",
+ "13",
+ "15A",
+ "N4",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 1300,
+ "name": {
+ "original": "Avda. de Castelao 68"
+ },
+ "latitude": 42.217466378,
+ "longitude": -8.751245499,
+ "lines": [
+ "C3d",
+ "4A",
+ "4C",
+ "5B",
+ "10",
+ "12A",
+ "13",
+ "15A",
+ "N4",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 1310,
+ "name": {
+ "original": "Avda. de Castelao 73"
+ },
+ "latitude": 42.217705528,
+ "longitude": -8.747753325,
+ "lines": [
+ "C3i",
+ "4A",
+ "4C",
+ "10",
+ "11",
+ "12A",
+ "15A",
+ "N1",
+ "N4",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 1320,
+ "name": {
+ "original": "Avda. de Castelao 87"
+ },
+ "latitude": 42.217302224,
+ "longitude": -8.751104752,
+ "lines": [
+ "C3i",
+ "10",
+ "12A",
+ "N1",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 1330,
+ "name": {
+ "original": "Avda. de Castrelos (Pavillón)"
+ },
+ "latitude": 42.219553947,
+ "longitude": -8.732509436,
+ "lines": [
+ "A",
+ "16",
+ "23",
+ "27",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 1340,
+ "name": {
+ "original": "Avda. de Castrelos (Parque)"
+ },
+ "latitude": 42.212870645,
+ "longitude": -8.732131792,
+ "lines": [
+ "27",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 1350,
+ "name": {
+ "original": "Avda. de Castrelos 121"
+ },
+ "latitude": 42.208026488,
+ "longitude": -8.7312098,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "27"
+ ]
+ },
+ {
+ "stopId": 1360,
+ "name": {
+ "original": "Avda. de Castrelos 16"
+ },
+ "latitude": 42.219613217,
+ "longitude": -8.732629194,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "27",
+ "H2",
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 1380,
+ "name": {
+ "original": "Avda. de Castrelos 179"
+ },
+ "latitude": 42.20533568,
+ "longitude": -8.730078621,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 1390,
+ "name": {
+ "original": "Avda. de Castrelos 186"
+ },
+ "latitude": 42.212735556,
+ "longitude": -8.732314182,
+ "lines": [
+ "A",
+ "7",
+ "12B",
+ "17",
+ "27",
+ "U1",
+ "H2",
+ "H",
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 1400,
+ "name": {
+ "original": "Avda. de Castrelos 202"
+ },
+ "latitude": 42.210706683,
+ "longitude": -8.732237372,
+ "lines": [
+ "A",
+ "7",
+ "12B",
+ "17",
+ "27",
+ "U1",
+ "H1",
+ "H2",
+ "H",
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 1410,
+ "name": {
+ "original": "Avda. de Castrelos 13"
+ },
+ "latitude": 42.218060161,
+ "longitude": -8.732450427,
+ "lines": [
+ "A",
+ "16",
+ "23",
+ "27",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 1420,
+ "name": {
+ "original": "Avda. de Castrelos 297"
+ },
+ "latitude": 42.201440099,
+ "longitude": -8.726409762,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 1430,
+ "name": {
+ "original": "Avda. de Castrelos 318"
+ },
+ "latitude": 42.203408408,
+ "longitude": -8.728817983,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 1440,
+ "name": {
+ "original": "Avda. de Castrelos 339"
+ },
+ "latitude": 42.198480135,
+ "longitude": -8.723827649,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 1450,
+ "name": {
+ "original": "Avda. de Castrelos 366"
+ },
+ "latitude": 42.201044695,
+ "longitude": -8.726112037,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 1460,
+ "name": {
+ "original": "Avda. de Castrelos 396"
+ },
+ "latitude": 42.198867605,
+ "longitude": -8.72460549,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 1470,
+ "name": {
+ "original": "Avda. de Castrelos 399"
+ },
+ "latitude": 42.194996678,
+ "longitude": -8.72097155,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 1480,
+ "name": {
+ "original": "Avda. de Castrelos 526"
+ },
+ "latitude": 42.19015727,
+ "longitude": -8.72109012,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 1490,
+ "name": {
+ "original": "Avda. de Castrelos 67"
+ },
+ "latitude": 42.210613294,
+ "longitude": -8.732057664,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "27",
+ "H1",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 1500,
+ "name": {
+ "original": "Avda. de Castrelos 58"
+ },
+ "latitude": 42.217084821,
+ "longitude": -8.732530893,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "27",
+ "H2",
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 1510,
+ "name": {
+ "original": "Avda. da Ponte 80"
+ },
+ "latitude": 42.215203365,
+ "longitude": -8.670416197,
+ "lines": [
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 1520,
+ "name": {
+ "original": "Avda. da Ponte 83"
+ },
+ "latitude": 42.215400704,
+ "longitude": -8.671308533,
+ "lines": [
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 1530,
+ "name": {
+ "original": "Avda. da Ponte (fronte Grupo S. Gorxal)"
+ },
+ "latitude": 42.212814417,
+ "longitude": -8.670537674,
+ "lines": [
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 1540,
+ "name": {
+ "original": "Avda. da Ponte 15"
+ },
+ "latitude": 42.221677429,
+ "longitude": -8.66978207,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 1550,
+ "name": {
+ "original": "Avda. da Ponte 18"
+ },
+ "latitude": 42.221299207,
+ "longitude": -8.670013709,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 1560,
+ "name": {
+ "original": "Avda. da Ponte 31"
+ },
+ "latitude": 42.219388605,
+ "longitude": -8.669172606,
+ "lines": [
+ "15A",
+ "15B"
+ ]
+ },
+ {
+ "stopId": 1570,
+ "name": {
+ "original": "Avda. da Ponte 47"
+ },
+ "latitude": 42.217957539,
+ "longitude": -8.669369577,
+ "lines": [
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 1580,
+ "name": {
+ "original": "Avda. da Ponte 54"
+ },
+ "latitude": 42.218393587,
+ "longitude": -8.669480106,
+ "lines": [
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 1590,
+ "name": {
+ "original": "Avda. de Galicia (Parque Riouxa)"
+ },
+ "latitude": 42.256667905,
+ "longitude": -8.682575386,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1600,
+ "name": {
+ "original": "Avda. de Galicia 103"
+ },
+ "latitude": 42.251389209,
+ "longitude": -8.689369833,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1610,
+ "name": {
+ "original": "Avda. de Galicia 146"
+ },
+ "latitude": 42.251376198,
+ "longitude": -8.68920404,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 1620,
+ "name": {
+ "original": "Avda. de Galicia 139"
+ },
+ "latitude": 42.253987165,
+ "longitude": -8.686196616,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1630,
+ "name": {
+ "original": "Avda. de Galicia 165"
+ },
+ "latitude": 42.255177674,
+ "longitude": -8.684734482,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1640,
+ "name": {
+ "original": "Avda. de Galicia 200"
+ },
+ "latitude": 42.254950083,
+ "longitude": -8.684862748,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 1650,
+ "name": {
+ "original": "Avda. de Galicia 238"
+ },
+ "latitude": 42.256910715,
+ "longitude": -8.68201353,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 1660,
+ "name": {
+ "original": "Avda. de Galicia 280"
+ },
+ "latitude": 42.259217959,
+ "longitude": -8.679666503,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 1670,
+ "name": {
+ "original": "Avda. de Galicia 285"
+ },
+ "latitude": 42.258365967,
+ "longitude": -8.680508997,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1680,
+ "name": {
+ "original": "Avda. de Galicia (Parque Cruce Balbarda)"
+ },
+ "latitude": 42.251327471,
+ "longitude": -8.69260735,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 1690,
+ "name": {
+ "original": "Avda. de Galicia 71"
+ },
+ "latitude": 42.251420909,
+ "longitude": -8.692153216,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1710,
+ "name": {
+ "original": "Avda. de Vigo 6"
+ },
+ "latitude": 42.274450823,
+ "longitude": -8.667138233,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1720,
+ "name": {
+ "original": "Avda. de Vigo 95"
+ },
+ "latitude": 42.270480988,
+ "longitude": -8.667726374,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 1730,
+ "name": {
+ "original": "Avda. de Vigo 129"
+ },
+ "latitude": 42.267833798,
+ "longitude": -8.671345739,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 1740,
+ "name": {
+ "original": "Avda. de Vigo 120"
+ },
+ "latitude": 42.27068743,
+ "longitude": -8.668057842,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1750,
+ "name": {
+ "original": "Avda. de Vigo 161"
+ },
+ "latitude": 42.266305919,
+ "longitude": -8.672818918,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 1760,
+ "name": {
+ "original": "Avda. de Vigo 201"
+ },
+ "latitude": 42.26408966,
+ "longitude": -8.674082239,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 1770,
+ "name": {
+ "original": "Avda. de Vigo (Alameda de Rosalía de Castro)"
+ },
+ "latitude": 42.26785896,
+ "longitude": -8.671440263,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1780,
+ "name": {
+ "original": "Avda. de Vigo 230"
+ },
+ "latitude": 42.266245291,
+ "longitude": -8.672965754,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1790,
+ "name": {
+ "original": "Avda. de Vigo 261 (Cuatro Puentes)"
+ },
+ "latitude": 42.261621089,
+ "longitude": -8.677207279,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 1800,
+ "name": {
+ "original": "Avda. de Vigo 266"
+ },
+ "latitude": 42.263995234,
+ "longitude": -8.674224503,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1810,
+ "name": {
+ "original": "Avda. de Vigo 320"
+ },
+ "latitude": 42.262068498,
+ "longitude": -8.676736193,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1820,
+ "name": {
+ "original": "Avda. de Vigo 49"
+ },
+ "latitude": 42.271878394,
+ "longitude": -8.666356304,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 1830,
+ "name": {
+ "original": "Avda. de Vigo 11"
+ },
+ "latitude": 42.274038501,
+ "longitude": -8.666949932,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 1840,
+ "name": {
+ "original": "Avda. de Vigo 72"
+ },
+ "latitude": 42.27159436,
+ "longitude": -8.666389735,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 1850,
+ "name": {
+ "original": "Avda. de Europa (antes Camiño Freixeiro)"
+ },
+ "latitude": 42.216135691,
+ "longitude": -8.759632243,
+ "lines": [
+ "C3d",
+ "C3i",
+ "4A",
+ "12A",
+ "15A"
+ ]
+ },
+ {
+ "stopId": 1860,
+ "name": {
+ "original": "Avda. de Europa (cruce Rúa da Pardaíña)"
+ },
+ "latitude": 42.216741568,
+ "longitude": -8.757129742,
+ "lines": [
+ "C3d",
+ "C3i",
+ "4A",
+ "12A",
+ "15A"
+ ]
+ },
+ {
+ "stopId": 1870,
+ "name": {
+ "original": "Avda. de Europa 102"
+ },
+ "latitude": 42.211235082,
+ "longitude": -8.773459294,
+ "lines": [
+ "C3d",
+ "C3i",
+ "15A"
+ ]
+ },
+ {
+ "stopId": 1880,
+ "name": {
+ "original": "Avda. de Europa (cruce Rúa das Teixugueiras)"
+ },
+ "latitude": 42.216687933,
+ "longitude": -8.756794466,
+ "lines": [
+ "C3d",
+ "C3i",
+ "4A",
+ "4C",
+ "12A",
+ "15A",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 1890,
+ "name": {
+ "original": "Avda. de Europa 23"
+ },
+ "latitude": 42.215913672,
+ "longitude": -8.759904797,
+ "lines": [
+ "C3d",
+ "C3i",
+ "4A",
+ "4C",
+ "12A",
+ "15A",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 1900,
+ "name": {
+ "original": "Avda. de Europa (cruce Rúa do Bravo)"
+ },
+ "latitude": 42.211855119,
+ "longitude": -8.766755158,
+ "lines": [
+ "C3d",
+ "C3i",
+ "4C",
+ "15A",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 1910,
+ "name": {
+ "original": "Avda. de Europa (fronte cruce Rúa do Bravo)"
+ },
+ "latitude": 42.211905694,
+ "longitude": -8.766999036,
+ "lines": [
+ "C3d",
+ "C3i",
+ "15A"
+ ]
+ },
+ {
+ "stopId": 1920,
+ "name": {
+ "original": "Avda. de Europa 101"
+ },
+ "latitude": 42.211066417,
+ "longitude": -8.772953743,
+ "lines": [
+ "C3d",
+ "C3i",
+ "4C",
+ "15A",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 1930,
+ "name": {
+ "original": "Estrada de Madrid (fronte Seminario)"
+ },
+ "latitude": 42.21474886,
+ "longitude": -8.69897918,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 1940,
+ "name": {
+ "original": "Avda. de Madrid 136"
+ },
+ "latitude": 42.218338954,
+ "longitude": -8.703817429,
+ "lines": [
+ "12A",
+ "12B",
+ "13"
+ ]
+ },
+ {
+ "stopId": 1950,
+ "name": {
+ "original": "Avda. de Madrid 124"
+ },
+ "latitude": 42.220567154,
+ "longitude": -8.706419628,
+ "lines": [
+ "12A",
+ "12B",
+ "13"
+ ]
+ },
+ {
+ "stopId": 1960,
+ "name": {
+ "original": "Avda. de Madrid 62"
+ },
+ "latitude": 42.224676782,
+ "longitude": -8.711832326,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "U2",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 1970,
+ "name": {
+ "original": "Avda. de Madrid 57"
+ },
+ "latitude": 42.223965709,
+ "longitude": -8.710062068,
+ "lines": [
+ "12A",
+ "12B",
+ "13"
+ ]
+ },
+ {
+ "stopId": 1980,
+ "name": {
+ "original": "Estrada de Madrid (Seminario)"
+ },
+ "latitude": 42.214703324,
+ "longitude": -8.699378397,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 1990,
+ "name": {
+ "original": "Avda. de Madrid (cruce Camiño do Raviso)"
+ },
+ "latitude": 42.222130198,
+ "longitude": -8.708300774,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 2000,
+ "name": {
+ "original": "Avda. de Madrid 133"
+ },
+ "latitude": 42.220728012,
+ "longitude": -8.70612292,
+ "lines": [
+ "12A",
+ "12B",
+ "13"
+ ]
+ },
+ {
+ "stopId": 2010,
+ "name": {
+ "original": "Avda. de Madrid 195"
+ },
+ "latitude": 42.218213283,
+ "longitude": -8.703163426,
+ "lines": [
+ "12A",
+ "12B",
+ "13"
+ ]
+ },
+ {
+ "stopId": 2020,
+ "name": {
+ "original": "Avda. de Madrid 2"
+ },
+ "latitude": 42.228518615,
+ "longitude": -8.719214126,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 2030,
+ "name": {
+ "original": "Avda. de Madrid 28"
+ },
+ "latitude": 42.226744428,
+ "longitude": -8.716268699,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 2040,
+ "name": {
+ "original": "Avda. de Madrid (trasera Colexio Hogar)"
+ },
+ "latitude": 42.226835791,
+ "longitude": -8.715823453,
+ "lines": [
+ "12A",
+ "12B",
+ "13"
+ ]
+ },
+ {
+ "stopId": 2060,
+ "name": {
+ "original": "Avda. de Redondela 122"
+ },
+ "latitude": 42.259945558,
+ "longitude": -8.672608434,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 2070,
+ "name": {
+ "original": "Avda. de Redondela 109"
+ },
+ "latitude": 42.259481393,
+ "longitude": -8.67292487,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 2080,
+ "name": {
+ "original": "Avda. de Redondela 19"
+ },
+ "latitude": 42.266569717,
+ "longitude": -8.667160768,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 2090,
+ "name": {
+ "original": "Avda. de Redondela 32"
+ },
+ "latitude": 42.272074281,
+ "longitude": -8.664593691,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 2100,
+ "name": {
+ "original": "Avda. de Redondela 47"
+ },
+ "latitude": 42.263186001,
+ "longitude": -8.668939094,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 2110,
+ "name": {
+ "original": "Avda. de Redondela 70"
+ },
+ "latitude": 42.263286688,
+ "longitude": -8.668985036,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 2130,
+ "name": {
+ "original": "Avda. de Redondela (Instituto)"
+ },
+ "latitude": 42.266758811,
+ "longitude": -8.667247828,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 2140,
+ "name": {
+ "original": "Avda. de Samil (Verbum)"
+ },
+ "latitude": 42.213799707,
+ "longitude": -8.774374426,
+ "lines": [
+ "10",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 2150,
+ "name": {
+ "original": "Avda. de Samil (fronte Praia da Fonte)"
+ },
+ "latitude": 42.221416498,
+ "longitude": -8.773724153,
+ "lines": [
+ "10",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 2160,
+ "name": {
+ "original": "Avda. de Samil 15"
+ },
+ "latitude": 42.215907608,
+ "longitude": -8.774702031,
+ "lines": [
+ "10",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 2170,
+ "name": {
+ "original": "Avda. de Samil 35"
+ },
+ "latitude": 42.210171416,
+ "longitude": -8.774585056,
+ "lines": [
+ "C3d",
+ "4C",
+ "10"
+ ]
+ },
+ {
+ "stopId": 2180,
+ "name": {
+ "original": "Avda. de Samil 67"
+ },
+ "latitude": 42.206809895,
+ "longitude": -8.776206766,
+ "lines": [
+ "C3d",
+ "4C",
+ "10"
+ ]
+ },
+ {
+ "stopId": 2190,
+ "name": {
+ "original": "Avda. de Samil 81"
+ },
+ "latitude": 42.205147646,
+ "longitude": -8.77674534,
+ "lines": [
+ "C3d",
+ "4C",
+ "10"
+ ]
+ },
+ {
+ "stopId": 2200,
+ "name": {
+ "original": "Avda. de Santa Mariña 110"
+ },
+ "latitude": 42.22271748,
+ "longitude": -8.656176614,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 2210,
+ "name": {
+ "original": "Avda. de Santa Mariña 137"
+ },
+ "latitude": 42.222538699,
+ "longitude": -8.656616496,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 2220,
+ "name": {
+ "original": "Avda. de Santa Mariña 17"
+ },
+ "latitude": 42.220338634,
+ "longitude": -8.668666271,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 2230,
+ "name": {
+ "original": "Avda. de Santa Mariña 52"
+ },
+ "latitude": 42.222347963,
+ "longitude": -8.662841482,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 2240,
+ "name": {
+ "original": "Avda. de Santa Mariña 77"
+ },
+ "latitude": 42.222432209,
+ "longitude": -8.662773458,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 2250,
+ "name": {
+ "original": "Avda. de Santa Mariña (cruce Camiño do Narxo)"
+ },
+ "latitude": 42.220826115,
+ "longitude": -8.659651094,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 2260,
+ "name": {
+ "original": "Avda. de Santa Mariña 18"
+ },
+ "latitude": 42.220504663,
+ "longitude": -8.668053014,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 2270,
+ "name": {
+ "original": "Avda. de Santa Mariña 103"
+ },
+ "latitude": 42.22113201,
+ "longitude": -8.658591621,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 2280,
+ "name": {
+ "original": "Baixada á Laxe 31"
+ },
+ "latitude": 42.21650849,
+ "longitude": -8.719175368,
+ "lines": [
+ "18A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 2290,
+ "name": {
+ "original": "Baixada á Laxe 44"
+ },
+ "latitude": 42.216415126,
+ "longitude": -8.719355076,
+ "lines": [
+ "18A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 2300,
+ "name": {
+ "original": "Baixada á Ponte Nova 13"
+ },
+ "latitude": 42.22078735,
+ "longitude": -8.722722261,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 2310,
+ "name": {
+ "original": "Baixada á Praia (fronte 187)"
+ },
+ "latitude": 42.173398721,
+ "longitude": -8.811050666,
+ "lines": [
+ "C3d",
+ "10",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 2320,
+ "name": {
+ "original": "Baixada á Praia 121"
+ },
+ "latitude": 42.172541892,
+ "longitude": -8.809133287,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 2330,
+ "name": {
+ "original": "Avda. de Ricardo Mella 357"
+ },
+ "latitude": 42.173316968,
+ "longitude": -8.81100291,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 2340,
+ "name": {
+ "original": "Baixada á Praia 44"
+ },
+ "latitude": 42.167981444,
+ "longitude": -8.806504239,
+ "lines": [
+ "C3d",
+ "10",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 2350,
+ "name": {
+ "original": "Baixada á Praia 74"
+ },
+ "latitude": 42.169850316,
+ "longitude": -8.808861828,
+ "lines": [
+ "C3d",
+ "10",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 2360,
+ "name": {
+ "original": "Baixada á Praia (Parque C.Cívico)"
+ },
+ "latitude": 42.167825345,
+ "longitude": -8.806386831,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 2370,
+ "name": {
+ "original": "Baixada á Praia 94"
+ },
+ "latitude": 42.172705652,
+ "longitude": -8.809114415,
+ "lines": [
+ "C3d",
+ "10",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 2380,
+ "name": {
+ "original": "Baixada á Praia 101"
+ },
+ "latitude": 42.169719111,
+ "longitude": -8.808832324,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 2390,
+ "name": {
+ "original": "Baixada á Salgueira 49"
+ },
+ "latitude": 42.224020371,
+ "longitude": -8.716787891,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 2410,
+ "name": {
+ "original": "Baixada ao Río 31"
+ },
+ "latitude": 42.209020914,
+ "longitude": -8.702331689,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 2420,
+ "name": {
+ "original": "Rúa do Cacheno (Lavadero)"
+ },
+ "latitude": 42.208468606,
+ "longitude": -8.702143934,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 2430,
+ "name": {
+ "original": "Rúa de Barcelona 64"
+ },
+ "latitude": 42.223917068,
+ "longitude": -8.726168827,
+ "lines": [
+ "C1"
+ ]
+ },
+ {
+ "stopId": 2440,
+ "name": {
+ "original": "Rúa de Barcelona 2"
+ },
+ "latitude": 42.228315534,
+ "longitude": -8.721741958,
+ "lines": [
+ "C1"
+ ]
+ },
+ {
+ "stopId": 2450,
+ "name": {
+ "original": "Rúa de Barcelona 32"
+ },
+ "latitude": 42.226024692,
+ "longitude": -8.723390804,
+ "lines": [
+ "C1"
+ ]
+ },
+ {
+ "stopId": 2460,
+ "name": {
+ "original": "Rúa de Xeme 59"
+ },
+ "latitude": 42.203300943,
+ "longitude": -8.696320858,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 2490,
+ "name": {
+ "original": "Rúa das Coutadas 57"
+ },
+ "latitude": 42.194635725,
+ "longitude": -8.699504032,
+ "lines": [
+ "6",
+ "27"
+ ]
+ },
+ {
+ "stopId": 2500,
+ "name": {
+ "original": "Rúa de Ramiro Pascual (Igrexa)"
+ },
+ "latitude": 42.191950062,
+ "longitude": -8.707193511,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 2510,
+ "name": {
+ "original": "Avda. de Castrelos 439"
+ },
+ "latitude": 42.191323803,
+ "longitude": -8.721049887,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 2520,
+ "name": {
+ "original": "Estrada de Bembrive 238"
+ },
+ "latitude": 42.204563665,
+ "longitude": -8.687025359,
+ "lines": [
+ "6",
+ "14"
+ ]
+ },
+ {
+ "stopId": 2540,
+ "name": {
+ "original": "Bouzas (Rotonda de Las Anclas)"
+ },
+ "latitude": 42.22513153,
+ "longitude": -8.751597027,
+ "lines": [
+ "6",
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 2550,
+ "name": {
+ "original": "Camiño da Brea 10"
+ },
+ "latitude": 42.204086577,
+ "longitude": -8.704832445,
+ "lines": [
+ "18A",
+ "18B"
+ ]
+ },
+ {
+ "stopId": 2560,
+ "name": {
+ "original": "Camiño da Brea 37"
+ },
+ "latitude": 42.204297185,
+ "longitude": -8.704942415,
+ "lines": [
+ "18A",
+ "18B"
+ ]
+ },
+ {
+ "stopId": 2570,
+ "name": {
+ "original": "Camiño da Brea 54"
+ },
+ "latitude": 42.207146083,
+ "longitude": -8.704526775,
+ "lines": [
+ "18A",
+ "18B"
+ ]
+ },
+ {
+ "stopId": 2580,
+ "name": {
+ "original": "Camiño da Brea 69"
+ },
+ "latitude": 42.207235488,
+ "longitude": -8.704631381,
+ "lines": [
+ "18A",
+ "18B"
+ ]
+ },
+ {
+ "stopId": 2590,
+ "name": {
+ "original": "Rúa da Cabalaría 94"
+ },
+ "latitude": 42.232442621,
+ "longitude": -8.689558541,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 2600,
+ "name": {
+ "original": "Rúa da Cabalaría 153"
+ },
+ "latitude": 42.235437997,
+ "longitude": -8.689019794,
+ "lines": [
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 2610,
+ "name": {
+ "original": "Rúa da Cabalaría 14"
+ },
+ "latitude": 42.233426101,
+ "longitude": -8.693298639,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 2620,
+ "name": {
+ "original": "Rúa da Cabalaría 186"
+ },
+ "latitude": 42.235420737,
+ "longitude": -8.68894034,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 2630,
+ "name": {
+ "original": "Rúa da Cabalaría (cruce Subida ao Rosal Florido)"
+ },
+ "latitude": 42.233483693,
+ "longitude": -8.693266452,
+ "lines": [
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 2640,
+ "name": {
+ "original": "Rúa da Cabalaría 67"
+ },
+ "latitude": 42.232559793,
+ "longitude": -8.689848219,
+ "lines": [
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 2735,
+ "name": {
+ "original": "Rúa da Cachamuíña (Concello)"
+ },
+ "latitude": 42.235033314,
+ "longitude": -8.727202198,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 2740,
+ "name": {
+ "original": "Rúa do Cacheno 75"
+ },
+ "latitude": 42.207451401,
+ "longitude": -8.701194432,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 2750,
+ "name": {
+ "original": "Rúa do Cacheno 28"
+ },
+ "latitude": 42.20671834,
+ "longitude": -8.699160127,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 2760,
+ "name": {
+ "original": "Rúa do Cacheno 66"
+ },
+ "latitude": 42.208194443,
+ "longitude": -8.701518979,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 2770,
+ "name": {
+ "original": "Rúa do Cacheno 49"
+ },
+ "latitude": 42.20648703,
+ "longitude": -8.698760561,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 2780,
+ "name": {
+ "original": "Avda. das Camelias 135"
+ },
+ "latitude": 42.222387178,
+ "longitude": -8.731207698,
+ "lines": [
+ "4A",
+ "4C",
+ "5A",
+ "5B",
+ "11",
+ "12A",
+ "12B",
+ "16",
+ "17",
+ "27",
+ "N1",
+ "LZH"
+ ]
+ },
+ {
+ "stopId": 2790,
+ "name": {
+ "original": "Avda. das Camelias 37"
+ },
+ "latitude": 42.230566426,
+ "longitude": -8.730086804,
+ "lines": [
+ "4A",
+ "4C",
+ "11",
+ "12B",
+ "17",
+ "27",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 2800,
+ "name": {
+ "original": "Avda. das Camelias 46"
+ },
+ "latitude": 42.230291959,
+ "longitude": -8.730279255,
+ "lines": [
+ "4A",
+ "4C",
+ "7",
+ "12B",
+ "17",
+ "27",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 2810,
+ "name": {
+ "original": "Avda. das Camelias 80"
+ },
+ "latitude": 42.227601839,
+ "longitude": -8.730236339,
+ "lines": [
+ "4A",
+ "4C",
+ "7",
+ "12B",
+ "17",
+ "27",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 2820,
+ "name": {
+ "original": "Avda. das Camelias (Praza 8 de Marzo)"
+ },
+ "latitude": 42.227403959,
+ "longitude": -8.729948584,
+ "lines": [
+ "4A",
+ "4C",
+ "11",
+ "12B",
+ "17",
+ "27",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 2830,
+ "name": {
+ "original": "Rúa de Camilo Veiga 33"
+ },
+ "latitude": 42.22243855,
+ "longitude": -8.751978552,
+ "lines": [
+ "C3d",
+ "13",
+ "15B",
+ "15C",
+ "U1",
+ "H"
+ ]
+ },
+ {
+ "stopId": 2840,
+ "name": {
+ "original": "Rúa da Goleta 3"
+ },
+ "latitude": 42.241698248,
+ "longitude": -8.665209215,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 2850,
+ "name": {
+ "original": "Rúa da Goleta 2"
+ },
+ "latitude": 42.241676405,
+ "longitude": -8.665026825,
+ "lines": [
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 2870,
+ "name": {
+ "original": "Rúa de Cantabria (Compañía Suministradora de Auga)"
+ },
+ "latitude": 42.237397409,
+ "longitude": -8.694169154,
+ "lines": [
+ "4A",
+ "24"
+ ]
+ },
+ {
+ "stopId": 2880,
+ "name": {
+ "original": "Rúa de Cantabria 148"
+ },
+ "latitude": 42.239331582,
+ "longitude": -8.692983618,
+ "lines": [
+ "4A",
+ "24"
+ ]
+ },
+ {
+ "stopId": 2910,
+ "name": {
+ "original": "Rúa de Cantabria 212"
+ },
+ "latitude": 42.241655974,
+ "longitude": -8.692827561,
+ "lines": [
+ "4A",
+ "24"
+ ]
+ },
+ {
+ "stopId": 2920,
+ "name": {
+ "original": "Rúa de Cantabria 45"
+ },
+ "latitude": 42.235852081,
+ "longitude": -8.695592417,
+ "lines": [
+ "4A",
+ "24"
+ ]
+ },
+ {
+ "stopId": 2930,
+ "name": {
+ "original": "Rúa de Cantabria 58"
+ },
+ "latitude": 42.235377808,
+ "longitude": -8.695695331,
+ "lines": [
+ "4A",
+ "24"
+ ]
+ },
+ {
+ "stopId": 2950,
+ "name": {
+ "original": "Rúa dos Canteiros 4"
+ },
+ "latitude": 42.204307169,
+ "longitude": -8.729719236,
+ "lines": [
+ "12B",
+ "17"
+ ]
+ },
+ {
+ "stopId": 2960,
+ "name": {
+ "original": "Rúa dos Canteiros 101"
+ },
+ "latitude": 42.20073699,
+ "longitude": -8.738895516,
+ "lines": [
+ "12B",
+ "17"
+ ]
+ },
+ {
+ "stopId": 2970,
+ "name": {
+ "original": "Rúa dos Canteiros 116"
+ },
+ "latitude": 42.201317829,
+ "longitude": -8.735636365,
+ "lines": [
+ "12B",
+ "17"
+ ]
+ },
+ {
+ "stopId": 2980,
+ "name": {
+ "original": "Rúa dos Canteiros 164"
+ },
+ "latitude": 42.200758846,
+ "longitude": -8.739043037,
+ "lines": [
+ "12B",
+ "17"
+ ]
+ },
+ {
+ "stopId": 2990,
+ "name": {
+ "original": "Rúa dos Canteiros 9"
+ },
+ "latitude": 42.203792573,
+ "longitude": -8.729630723,
+ "lines": [
+ "12B",
+ "17"
+ ]
+ },
+ {
+ "stopId": 3000,
+ "name": {
+ "original": "Rúa dos Canteiros 49"
+ },
+ "latitude": 42.201188677,
+ "longitude": -8.733208966,
+ "lines": [
+ "12B",
+ "17"
+ ]
+ },
+ {
+ "stopId": 3010,
+ "name": {
+ "original": "Rúa dos Canteiros 73"
+ },
+ "latitude": 42.20121252,
+ "longitude": -8.735711467,
+ "lines": [
+ "12B",
+ "17"
+ ]
+ },
+ {
+ "stopId": 3020,
+ "name": {
+ "original": "Rúa dos Canteiros 76"
+ },
+ "latitude": 42.201218481,
+ "longitude": -8.733509374,
+ "lines": [
+ "12B",
+ "17"
+ ]
+ },
+ {
+ "stopId": 3030,
+ "name": {
+ "original": "Avda. de Castrelos 458"
+ },
+ "latitude": 42.194702579,
+ "longitude": -8.721025195,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 3050,
+ "name": {
+ "original": "Estrada de Casás (cruce Camiño da Pedra Branca)"
+ },
+ "latitude": 42.192360267,
+ "longitude": -8.756680959,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 3052,
+ "name": {
+ "original": "Avda. Arquitecto Antonio Palacios (cruce Rúa Ricardo Torres)"
+ },
+ "latitude": 42.216966631,
+ "longitude": -8.729813845,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 3060,
+ "name": {
+ "original": "Rúa da Ceboleira 30"
+ },
+ "latitude": 42.224698049,
+ "longitude": -8.701749123,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3070,
+ "name": {
+ "original": "Rúa da Ceboleira 49"
+ },
+ "latitude": 42.224816342,
+ "longitude": -8.701565998,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3080,
+ "name": {
+ "original": "Avda. de Cesáreo Vázquez 136"
+ },
+ "latitude": 42.1873653089623,
+ "longitude": -8.800886236766305,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 3090,
+ "name": {
+ "original": "Avda. de Cesáreo Vázquez 182"
+ },
+ "latitude": 42.191019711713736,
+ "longitude": -8.799628565094565,
+ "lines": [
+ "C3d",
+ "10",
+ "11"
+ ]
+ },
+ {
+ "stopId": 3100,
+ "name": {
+ "original": "Avda. de Cesáreo Vázquez 99"
+ },
+ "latitude": 42.184766843,
+ "longitude": -8.802180879,
+ "lines": [
+ "11",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 3110,
+ "name": {
+ "original": "Avda. de Cesáreo Vázquez 74"
+ },
+ "latitude": 42.184416675,
+ "longitude": -8.802179713,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 3120,
+ "name": {
+ "original": "Avda. de Cesáreo Vázquez 141"
+ },
+ "latitude": 42.187488521491225,
+ "longitude": -8.801226626055183,
+ "lines": [
+ "11",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 3130,
+ "name": {
+ "original": "Avda. de Cesáreo Vázquez 169"
+ },
+ "latitude": 42.191024803868736,
+ "longitude": -8.799397387002196,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 3140,
+ "name": {
+ "original": "Estrada De Zamáns 255"
+ },
+ "latitude": 42.15800346,
+ "longitude": -8.686134691,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 3150,
+ "name": {
+ "original": "Estrada de Zamáns (cruce Igrexa)"
+ },
+ "latitude": 42.157661469,
+ "longitude": -8.685973759,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 3160,
+ "name": {
+ "original": "Camiño da Falcoa 10"
+ },
+ "latitude": 42.206593246,
+ "longitude": -8.721651793,
+ "lines": [
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 3170,
+ "name": {
+ "original": "Rúa das Coutadas 76"
+ },
+ "latitude": 42.220968446,
+ "longitude": -8.720400826,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 3180,
+ "name": {
+ "original": "Camiño de Quirós 106"
+ },
+ "latitude": 42.21955863,
+ "longitude": -8.711410119,
+ "lines": [
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 3190,
+ "name": {
+ "original": "Camiño de Quirós (cruce Rúa de Dona Cristina)"
+ },
+ "latitude": 42.219681782,
+ "longitude": -8.711385979,
+ "lines": [
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 3230,
+ "name": {
+ "original": "Rúa de Colón 27"
+ },
+ "latitude": 42.236471452,
+ "longitude": -8.720164905,
+ "lines": [
+ "A",
+ "5A",
+ "9B",
+ "11",
+ "15B",
+ "15C",
+ "16",
+ "17"
+ ]
+ },
+ {
+ "stopId": 3240,
+ "name": {
+ "original": "Rúa da Coruña 5"
+ },
+ "latitude": 42.22214729,
+ "longitude": -8.734167916,
+ "lines": [
+ "A",
+ "5A",
+ "5B",
+ "10",
+ "11",
+ "13",
+ "N4",
+ "U1",
+ "H1",
+ "H"
+ ]
+ },
+ {
+ "stopId": 3250,
+ "name": {
+ "original": "Rúa da Coruña 26"
+ },
+ "latitude": 42.222378625,
+ "longitude": -8.734134247,
+ "lines": [
+ "C1",
+ "A",
+ "10",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 3260,
+ "name": {
+ "original": "Rúa da Coruña 37"
+ },
+ "latitude": 42.226287696,
+ "longitude": -8.737475832,
+ "lines": [
+ "15B"
+ ]
+ },
+ {
+ "stopId": 3270,
+ "name": {
+ "original": "Rúa da Coruña (fronte 39)"
+ },
+ "latitude": 42.226490285,
+ "longitude": -8.73744901,
+ "lines": [
+ "10",
+ "15B"
+ ]
+ },
+ {
+ "stopId": 3280,
+ "name": {
+ "original": "Rúa de Manuel Lago Lago 1"
+ },
+ "latitude": 42.188898503,
+ "longitude": -8.776299224,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 3290,
+ "name": {
+ "original": "Rúa da Costa 13"
+ },
+ "latitude": 42.213397685,
+ "longitude": -8.72248211,
+ "lines": [
+ "A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 3300,
+ "name": {
+ "original": "Rúa da Costa 22"
+ },
+ "latitude": 42.211751348,
+ "longitude": -8.721691531,
+ "lines": [
+ "A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 3310,
+ "name": {
+ "original": "Rúa da Costa 74"
+ },
+ "latitude": 42.209233023,
+ "longitude": -8.720666368,
+ "lines": [
+ "A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 3320,
+ "name": {
+ "original": "Rúa da Costa 63"
+ },
+ "latitude": 42.210154848,
+ "longitude": -8.720902403,
+ "lines": [
+ "A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 3350,
+ "name": {
+ "original": "Rúa do Couto 1"
+ },
+ "latitude": 42.230911796,
+ "longitude": -8.722711253,
+ "lines": [
+ "12A",
+ "27"
+ ]
+ },
+ {
+ "stopId": 3360,
+ "name": {
+ "original": "Rúa do Doutor Canoa 8"
+ },
+ "latitude": 42.237803518,
+ "longitude": -8.704409031,
+ "lines": [
+ "31"
+ ]
+ },
+ {
+ "stopId": 3370,
+ "name": {
+ "original": "Estrada de Bembrive (cruce Rúa Eifonso)"
+ },
+ "latitude": 42.205676226,
+ "longitude": -8.693112047,
+ "lines": [
+ "6",
+ "14"
+ ]
+ },
+ {
+ "stopId": 3380,
+ "name": {
+ "original": "Cruce Eifonso"
+ },
+ "latitude": 42.205623108,
+ "longitude": -8.693239335,
+ "lines": [
+ "6",
+ "14"
+ ]
+ },
+ {
+ "stopId": 3390,
+ "name": {
+ "original": "Estrada de Bembrive 160"
+ },
+ "latitude": 42.205296644,
+ "longitude": -8.692558914,
+ "lines": [
+ "6",
+ "14"
+ ]
+ },
+ {
+ "stopId": 3400,
+ "name": {
+ "original": "Estrada da Gándara 22"
+ },
+ "latitude": 42.163584451,
+ "longitude": -8.716088728,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 3420,
+ "name": {
+ "original": "Avda. de Castrelos (Cemiterio de Pereiró)"
+ },
+ "latitude": 42.208071495,
+ "longitude": -8.731366082,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "27",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 3430,
+ "name": {
+ "original": "Avda. da Ponte 86 (Cemiterio)"
+ },
+ "latitude": 42.215621206,
+ "longitude": -8.67221512,
+ "lines": [
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 3450,
+ "name": {
+ "original": "Estrada de Camposancos 155"
+ },
+ "latitude": 42.194732286,
+ "longitude": -8.769245322,
+ "lines": [
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 3460,
+ "name": {
+ "original": "Estrada de Camposancos 19"
+ },
+ "latitude": 42.203197031,
+ "longitude": -8.753437723,
+ "lines": [
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 3470,
+ "name": {
+ "original": "Estrada de Camposancos 214"
+ },
+ "latitude": 42.193581709,
+ "longitude": -8.772496159,
+ "lines": [
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 3480,
+ "name": {
+ "original": "Estrada de Camposancos 141"
+ },
+ "latitude": 42.196095467,
+ "longitude": -8.766230519,
+ "lines": [
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 3490,
+ "name": {
+ "original": "Estrada de Camposancos 171"
+ },
+ "latitude": 42.193613504,
+ "longitude": -8.772222574,
+ "lines": [
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 3500,
+ "name": {
+ "original": "Estrada de Camposancos 190"
+ },
+ "latitude": 42.194553241,
+ "longitude": -8.770325583,
+ "lines": [
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 3510,
+ "name": {
+ "original": "Estrada de Camposancos 28"
+ },
+ "latitude": 42.202849227,
+ "longitude": -8.753870291,
+ "lines": [
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 3520,
+ "name": {
+ "original": "Estrada de Camposancos 75"
+ },
+ "latitude": 42.200000035,
+ "longitude": -8.759107913,
+ "lines": [
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 3530,
+ "name": {
+ "original": "Estrada de Camposancos 88"
+ },
+ "latitude": 42.199773285,
+ "longitude": -8.759958845,
+ "lines": [
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 3540,
+ "name": {
+ "original": "Estrada de Camposancos 138"
+ },
+ "latitude": 42.19617384,
+ "longitude": -8.766295392,
+ "lines": [
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 3550,
+ "name": {
+ "original": "Estrada de Bembrive (cruce Camiño Cova)"
+ },
+ "latitude": 42.19747765,
+ "longitude": -8.688158543,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3560,
+ "name": {
+ "original": "Estrada de Bembrive 104"
+ },
+ "latitude": 42.209126518,
+ "longitude": -8.695599845,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3570,
+ "name": {
+ "original": "Estrada de Bembrive 109"
+ },
+ "latitude": 42.209576972,
+ "longitude": -8.695580071,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3572,
+ "name": {
+ "original": "Estrada de Bembrive 110"
+ },
+ "latitude": 42.208457815,
+ "longitude": -8.695017358,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 3574,
+ "name": {
+ "original": "Estrada de Bembrive 129"
+ },
+ "latitude": 42.208533311,
+ "longitude": -8.694987854,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 3580,
+ "name": {
+ "original": "Estrada de Bembrive 180"
+ },
+ "latitude": 42.204440318,
+ "longitude": -8.690871804,
+ "lines": [
+ "6",
+ "14"
+ ]
+ },
+ {
+ "stopId": 3590,
+ "name": {
+ "original": "Estrada de Bembrive 195"
+ },
+ "latitude": 42.204458432,
+ "longitude": -8.690765895,
+ "lines": [
+ "6",
+ "14"
+ ]
+ },
+ {
+ "stopId": 3600,
+ "name": {
+ "original": "Estrada de Bembrive 22"
+ },
+ "latitude": 42.21246424,
+ "longitude": -8.697218847,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3610,
+ "name": {
+ "original": "Estrada de Bembrive 237"
+ },
+ "latitude": 42.204446407,
+ "longitude": -8.68809021,
+ "lines": [
+ "6",
+ "14"
+ ]
+ },
+ {
+ "stopId": 3620,
+ "name": {
+ "original": "Estrada de Bembrive 278"
+ },
+ "latitude": 42.202562995,
+ "longitude": -8.685437577,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3630,
+ "name": {
+ "original": "Estrada de Bembrive 269"
+ },
+ "latitude": 42.20274418,
+ "longitude": -8.684443742,
+ "lines": [
+ "6",
+ "14"
+ ]
+ },
+ {
+ "stopId": 3640,
+ "name": {
+ "original": "Estrada de Bembrive 315"
+ },
+ "latitude": 42.197359418,
+ "longitude": -8.688113004,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3650,
+ "name": {
+ "original": "Estrada de Bembrive 346"
+ },
+ "latitude": 42.199275919,
+ "longitude": -8.689032944,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3660,
+ "name": {
+ "original": "Estrada de Bembrive 39"
+ },
+ "latitude": 42.212227656,
+ "longitude": -8.697028614,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3670,
+ "name": {
+ "original": "Estrada de Bembrive 398"
+ },
+ "latitude": 42.194968863,
+ "longitude": -8.689201981,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3680,
+ "name": {
+ "original": "Estrada de Bembrive 64"
+ },
+ "latitude": 42.211144971,
+ "longitude": -8.69652789,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3690,
+ "name": {
+ "original": "Estrada de Bembrive 73"
+ },
+ "latitude": 42.210947345,
+ "longitude": -8.69627482,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3700,
+ "name": {
+ "original": "Estrada de Bembrive (cruce Camiño Riomao)"
+ },
+ "latitude": 42.194055385,
+ "longitude": -8.692079677,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3710,
+ "name": {
+ "original": "Estrada de Bembrive 363"
+ },
+ "latitude": 42.193846109,
+ "longitude": -8.69200489,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3720,
+ "name": {
+ "original": "Estrada de Bembrive 341"
+ },
+ "latitude": 42.1948986,
+ "longitude": -8.689213754,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3730,
+ "name": {
+ "original": "Estrada de Bembrive 301"
+ },
+ "latitude": 42.199203404,
+ "longitude": -8.688880116,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3740,
+ "name": {
+ "original": "Rúa de Canido (Igrexa)"
+ },
+ "latitude": 42.197431882,
+ "longitude": -8.790144971,
+ "lines": [
+ "C3d",
+ "4C",
+ "10"
+ ]
+ },
+ {
+ "stopId": 3750,
+ "name": {
+ "original": "Rúa de Canido (Praia de Canido)"
+ },
+ "latitude": 42.193128934,
+ "longitude": -8.797957944,
+ "lines": [
+ "10",
+ "11"
+ ]
+ },
+ {
+ "stopId": 3760,
+ "name": {
+ "original": "Rúa de Canido (fronte 119)"
+ },
+ "latitude": 42.1957321,
+ "longitude": -8.795211362,
+ "lines": [
+ "10",
+ "11"
+ ]
+ },
+ {
+ "stopId": 3770,
+ "name": {
+ "original": "Rúa de Canido 135"
+ },
+ "latitude": 42.1950348,
+ "longitude": -8.795701844,
+ "lines": [
+ "C3d",
+ "10",
+ "11"
+ ]
+ },
+ {
+ "stopId": 3780,
+ "name": {
+ "original": "Rúa de Canido 217"
+ },
+ "latitude": 42.193041793,
+ "longitude": -8.797590453,
+ "lines": [
+ "C3d",
+ "10",
+ "11"
+ ]
+ },
+ {
+ "stopId": 3790,
+ "name": {
+ "original": "Rúa de Canido 15"
+ },
+ "latitude": 42.201898697,
+ "longitude": -8.781706742,
+ "lines": [
+ "C3d",
+ "4C",
+ "10"
+ ]
+ },
+ {
+ "stopId": 3800,
+ "name": {
+ "original": "Rúa de Canido 26"
+ },
+ "latitude": 42.199639519,
+ "longitude": -8.785727373,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 3810,
+ "name": {
+ "original": "Rúa de Canido 55"
+ },
+ "latitude": 42.200106486,
+ "longitude": -8.785121194,
+ "lines": [
+ "C3d",
+ "4C",
+ "10"
+ ]
+ },
+ {
+ "stopId": 3820,
+ "name": {
+ "original": "Rúa do Falcoído 16"
+ },
+ "latitude": 42.182659954,
+ "longitude": -8.699663442,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 3830,
+ "name": {
+ "original": "Rúa do Falcoído (cruce Camiño Goaldino)"
+ },
+ "latitude": 42.180635405,
+ "longitude": -8.696958243,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 3840,
+ "name": {
+ "original": "Rúa do Falcoído (cruce Camiño das Presas)"
+ },
+ "latitude": 42.181019011,
+ "longitude": -8.696918009,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 3850,
+ "name": {
+ "original": "Estrada Clara Campoamor (cruce Estrada Marcosende)"
+ },
+ "latitude": 42.167933917,
+ "longitude": -8.692758811,
+ "lines": [
+ "A",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 3860,
+ "name": {
+ "original": "Estrada Clara Campoamor (cruce Rúa do Falcoído)"
+ },
+ "latitude": 42.168248021,
+ "longitude": -8.692608608,
+ "lines": [
+ "A",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 3870,
+ "name": {
+ "original": "Rúa do Falcoído 13"
+ },
+ "latitude": 42.182749988,
+ "longitude": -8.699953254,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 3880,
+ "name": {
+ "original": "Estrada da Coutada 44"
+ },
+ "latitude": 42.194829735,
+ "longitude": -8.699040324,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3890,
+ "name": {
+ "original": "Estrada da Coutada 39"
+ },
+ "latitude": 42.193625246,
+ "longitude": -8.7014263,
+ "lines": [
+ "6",
+ "27"
+ ]
+ },
+ {
+ "stopId": 3900,
+ "name": {
+ "original": "Estrada da Coutada 19"
+ },
+ "latitude": 42.19232927,
+ "longitude": -8.704749414,
+ "lines": [
+ "6",
+ "27"
+ ]
+ },
+ {
+ "stopId": 3910,
+ "name": {
+ "original": "Estrada da Coutada 68"
+ },
+ "latitude": 42.195404022,
+ "longitude": -8.695794851,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 3920,
+ "name": {
+ "original": "Estrada da Coutada 65"
+ },
+ "latitude": 42.195725689,
+ "longitude": -8.697154293,
+ "lines": [
+ "6",
+ "27"
+ ]
+ },
+ {
+ "stopId": 3930,
+ "name": {
+ "original": "Estrada da Coutada 12"
+ },
+ "latitude": 42.19287831,
+ "longitude": -8.703506202,
+ "lines": [
+ "6",
+ "27"
+ ]
+ },
+ {
+ "stopId": 3940,
+ "name": {
+ "original": "Estrada de Casás 83"
+ },
+ "latitude": 42.196165214,
+ "longitude": -8.759890011,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 3950,
+ "name": {
+ "original": "Estrada de Casás (cruce Camiño do Rial)"
+ },
+ "latitude": 42.196014193,
+ "longitude": -8.759919516,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 3960,
+ "name": {
+ "original": "Baixada do Castelo"
+ },
+ "latitude": 42.213984907,
+ "longitude": -8.682160511,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "15B",
+ "15C",
+ "31"
+ ]
+ },
+ {
+ "stopId": 3970,
+ "name": {
+ "original": "Estrada Vella de Madrid 190"
+ },
+ "latitude": 42.215872123,
+ "longitude": -8.679765298,
+ "lines": [
+ "12B",
+ "15B",
+ "15C",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 3980,
+ "name": {
+ "original": "Estrada Vella de Madrid (cruce Hospital)"
+ },
+ "latitude": 42.21620386,
+ "longitude": -8.681570425,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "15B",
+ "15C",
+ "31",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 4000,
+ "name": {
+ "original": "Estrada de Fragoselo 143"
+ },
+ "latitude": 42.182434997,
+ "longitude": -8.764713761,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 4010,
+ "name": {
+ "original": "Estrada de Fragoselo 170"
+ },
+ "latitude": 42.184148566,
+ "longitude": -8.767237312,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 4020,
+ "name": {
+ "original": "Estrada de Fragoselo 79"
+ },
+ "latitude": 42.187056187,
+ "longitude": -8.769605703,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 4030,
+ "name": {
+ "original": "Estrada de Fragoselo 111"
+ },
+ "latitude": 42.184118753,
+ "longitude": -8.767049558,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 4040,
+ "name": {
+ "original": "Estrada de Fragoselo 108"
+ },
+ "latitude": 42.18661248,
+ "longitude": -8.769574881,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 4050,
+ "name": {
+ "original": "Estrada de Fragoselo 196"
+ },
+ "latitude": 42.182111026,
+ "longitude": -8.76479691,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 4060,
+ "name": {
+ "original": "Estrada da Gándara 7"
+ },
+ "latitude": 42.164326088,
+ "longitude": -8.716892619,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4070,
+ "name": {
+ "original": "Estrada da Gándara 55"
+ },
+ "latitude": 42.162254659,
+ "longitude": -8.713740793,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4080,
+ "name": {
+ "original": "Estrada da Gándara 48"
+ },
+ "latitude": 42.162252671,
+ "longitude": -8.71400365,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4090,
+ "name": {
+ "original": "Estrada da Garrida 124"
+ },
+ "latitude": 42.170369621,
+ "longitude": -8.708506123,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4100,
+ "name": {
+ "original": "Estrada da Garrida 199"
+ },
+ "latitude": 42.16978602,
+ "longitude": -8.708967287,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4110,
+ "name": {
+ "original": "Estrada da Garrida 40"
+ },
+ "latitude": 42.165331611,
+ "longitude": -8.714862589,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4120,
+ "name": {
+ "original": "Estrada da Garrida 84"
+ },
+ "latitude": 42.166399713,
+ "longitude": -8.711237684,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4130,
+ "name": {
+ "original": "Estrada da Garrida 83"
+ },
+ "latitude": 42.16561193,
+ "longitude": -8.714589004,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4140,
+ "name": {
+ "original": "Estrada da Garrida (frente 80)"
+ },
+ "latitude": 42.166208944,
+ "longitude": -8.711992518,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4150,
+ "name": {
+ "original": "Estrada de Miraflores 69"
+ },
+ "latitude": 42.217620909,
+ "longitude": -8.712429612,
+ "lines": [
+ "18A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 4160,
+ "name": {
+ "original": "Estrada de Miraflores 36"
+ },
+ "latitude": 42.217849364,
+ "longitude": -8.710994711,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 4170,
+ "name": {
+ "original": "Estrada de Miraflores 64"
+ },
+ "latitude": 42.217345186,
+ "longitude": -8.713011334,
+ "lines": [
+ "18A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 4200,
+ "name": {
+ "original": "Estrada de Moledo 6"
+ },
+ "latitude": 42.215236994,
+ "longitude": -8.707384971,
+ "lines": [
+ "14",
+ "18A"
+ ]
+ },
+ {
+ "stopId": 4210,
+ "name": {
+ "original": "Estrada de Moledo 42"
+ },
+ "latitude": 42.212141078,
+ "longitude": -8.704563545,
+ "lines": [
+ "14",
+ "18A"
+ ]
+ },
+ {
+ "stopId": 4220,
+ "name": {
+ "original": "Estrada de Moledo 22"
+ },
+ "latitude": 42.213737557,
+ "longitude": -8.706283115,
+ "lines": [
+ "14",
+ "18A"
+ ]
+ },
+ {
+ "stopId": 4230,
+ "name": {
+ "original": "Estrada de Moledo 1"
+ },
+ "latitude": 42.215373761,
+ "longitude": -8.707378349,
+ "lines": [
+ "14",
+ "18A"
+ ]
+ },
+ {
+ "stopId": 4240,
+ "name": {
+ "original": "Estrada de Moledo 73"
+ },
+ "latitude": 42.212378349,
+ "longitude": -8.704729584,
+ "lines": [
+ "14",
+ "18A"
+ ]
+ },
+ {
+ "stopId": 4250,
+ "name": {
+ "original": "Estrada de Moledo 25"
+ },
+ "latitude": 42.213377597,
+ "longitude": -8.70601168,
+ "lines": [
+ "14",
+ "18A"
+ ]
+ },
+ {
+ "stopId": 4280,
+ "name": {
+ "original": "Rúa de Emilia Pardo Bazán 80"
+ },
+ "latitude": 42.224507337,
+ "longitude": -8.713465538,
+ "lines": [
+ "14",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 4290,
+ "name": {
+ "original": "Rúa de Emilia Pardo Bazán 3"
+ },
+ "latitude": 42.227785739,
+ "longitude": -8.719460889,
+ "lines": [
+ "14",
+ "18A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 4300,
+ "name": {
+ "original": "Rúa de Emilia Pardo Bazán 104"
+ },
+ "latitude": 42.22278924,
+ "longitude": -8.711754289,
+ "lines": [
+ "14",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 4310,
+ "name": {
+ "original": "Rúa de Emilia Pardo Bazán 111"
+ },
+ "latitude": 42.222713762,
+ "longitude": -8.711555806,
+ "lines": [
+ "14",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 4320,
+ "name": {
+ "original": "Rúa de Emilia Pardo Bazán 2"
+ },
+ "latitude": 42.227716225,
+ "longitude": -8.719750568,
+ "lines": [
+ "14",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 4330,
+ "name": {
+ "original": "Rúa de Emilia Pardo Bazán (cruce Baixada á Salgueira)"
+ },
+ "latitude": 42.226463146,
+ "longitude": -8.71614062,
+ "lines": [
+ "14",
+ "18A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 4340,
+ "name": {
+ "original": "Rúa de Emilia Pardo Bazán 54"
+ },
+ "latitude": 42.226112503,
+ "longitude": -8.715718376,
+ "lines": [
+ "14",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 4350,
+ "name": {
+ "original": "Rúa de Emilia Pardo Bazán 43"
+ },
+ "latitude": 42.224562952,
+ "longitude": -8.713390437,
+ "lines": [
+ "14",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 4440,
+ "name": {
+ "original": "Estrada de San Xoán 169"
+ },
+ "latitude": 42.183275038,
+ "longitude": -8.742437444,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 4450,
+ "name": {
+ "original": "Estrada de San Xoán 61"
+ },
+ "latitude": 42.185081179,
+ "longitude": -8.748029634,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 4460,
+ "name": {
+ "original": "Estrada de San Xoán 141"
+ },
+ "latitude": 42.18366712,
+ "longitude": -8.744402777,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 4490,
+ "name": {
+ "original": "Rúa de Manuel Lago Lago (fronte Colexio)"
+ },
+ "latitude": 42.187522648,
+ "longitude": -8.772977937,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 4500,
+ "name": {
+ "original": "Rúa de Manuel Lago Lago 20"
+ },
+ "latitude": 42.188761213,
+ "longitude": -8.775973652,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 4510,
+ "name": {
+ "original": "Rúa de Manuel Lago Lago (Colexio)"
+ },
+ "latitude": 42.187542924,
+ "longitude": -8.772887753,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 4520,
+ "name": {
+ "original": "Estrada das Plantas (Cidade Deportiva)"
+ },
+ "latitude": 42.175884385,
+ "longitude": -8.670789023,
+ "lines": [
+ "15C"
+ ]
+ },
+ {
+ "stopId": 4530,
+ "name": {
+ "original": "Estrada das Plantas (fronte cruce Camiño do Pouso)"
+ },
+ "latitude": 42.199681015,
+ "longitude": -8.668860289,
+ "lines": [
+ "15C"
+ ]
+ },
+ {
+ "stopId": 4540,
+ "name": {
+ "original": "Estrada das Plantas (fronte Viveiros)"
+ },
+ "latitude": 42.181370416,
+ "longitude": -8.667861084,
+ "lines": [
+ "15C"
+ ]
+ },
+ {
+ "stopId": 4550,
+ "name": {
+ "original": "Estrada das Plantas (cruce Camiño do Pouso)"
+ },
+ "latitude": 42.199945182,
+ "longitude": -8.669085873,
+ "lines": [
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 4560,
+ "name": {
+ "original": "Estrada de Valadares 233"
+ },
+ "latitude": 42.177017792,
+ "longitude": -8.721519832,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4570,
+ "name": {
+ "original": "Estrada de Valadares 100"
+ },
+ "latitude": 42.183987864,
+ "longitude": -8.724154939,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 4580,
+ "name": {
+ "original": "Estrada de Valadares 146"
+ },
+ "latitude": 42.180249473,
+ "longitude": -8.721850007,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 4590,
+ "name": {
+ "original": "Estrada de Valadares 173"
+ },
+ "latitude": 42.180670846,
+ "longitude": -8.721533506,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4600,
+ "name": {
+ "original": "Estrada de Valadares 212"
+ },
+ "latitude": 42.176419487,
+ "longitude": -8.72197849,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 4610,
+ "name": {
+ "original": "Estrada de Valadares 262"
+ },
+ "latitude": 42.172481228,
+ "longitude": -8.723905842,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 4620,
+ "name": {
+ "original": "Estrada de Valadares 329"
+ },
+ "latitude": 42.172330151,
+ "longitude": -8.723766367,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4630,
+ "name": {
+ "original": "Estrada de Valadares 406"
+ },
+ "latitude": 42.165721529,
+ "longitude": -8.720146278,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 4640,
+ "name": {
+ "original": "Estrada de Valadares (cruce Camiño do Canizo)"
+ },
+ "latitude": 42.18659932,
+ "longitude": -8.719619913,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4650,
+ "name": {
+ "original": "Estrada de Valadares 48"
+ },
+ "latitude": 42.186641056,
+ "longitude": -8.719853265,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 4660,
+ "name": {
+ "original": "Estrada de Valadares 99"
+ },
+ "latitude": 42.183997801,
+ "longitude": -8.723857214,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 4670,
+ "name": {
+ "original": "Estrada do Vao 27"
+ },
+ "latitude": 42.193836069,
+ "longitude": -8.776441689,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 4680,
+ "name": {
+ "original": "Estrada do Vao 116"
+ },
+ "latitude": 42.19429691,
+ "longitude": -8.786574405,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 4690,
+ "name": {
+ "original": "Estrada do Vao 153"
+ },
+ "latitude": 42.196307191,
+ "longitude": -8.790777973,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 4700,
+ "name": {
+ "original": "Estrada do Vao 46"
+ },
+ "latitude": 42.194014721,
+ "longitude": -8.777055245,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 4710,
+ "name": {
+ "original": "Estrada do Vao 65"
+ },
+ "latitude": 42.194064592,
+ "longitude": -8.781224067,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 4720,
+ "name": {
+ "original": "Estrada do Vao 90"
+ },
+ "latitude": 42.193937416,
+ "longitude": -8.781634173,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 4730,
+ "name": {
+ "original": "Estrada do Vao 91"
+ },
+ "latitude": 42.19405266,
+ "longitude": -8.78551292,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 4740,
+ "name": {
+ "original": "Estrada do Vao (Praia do Vao)"
+ },
+ "latitude": 42.196595321,
+ "longitude": -8.791019372,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 4750,
+ "name": {
+ "original": "Estrada Vella de Madrid 6"
+ },
+ "latitude": 42.213983883,
+ "longitude": -8.697464269,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "U2",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 4760,
+ "name": {
+ "original": "Estrada Vella de Madrid 34"
+ },
+ "latitude": 42.216359908,
+ "longitude": -8.693040769,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "U2",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 4770,
+ "name": {
+ "original": "Estrada Vella de Madrid 104"
+ },
+ "latitude": 42.219822923,
+ "longitude": -8.684198247,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "31",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 4780,
+ "name": {
+ "original": "Estrada Vella de Madrid 136"
+ },
+ "latitude": 42.220845201,
+ "longitude": -8.680581909,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "31",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 4790,
+ "name": {
+ "original": "Estrada Vella de Madrid 123"
+ },
+ "latitude": 42.220436017,
+ "longitude": -8.683690589,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "31"
+ ]
+ },
+ {
+ "stopId": 4800,
+ "name": {
+ "original": "Estrada Vella de Madrid 160"
+ },
+ "latitude": 42.218049288,
+ "longitude": -8.679899408,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "31",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 4810,
+ "name": {
+ "original": "Estrada Vella de Madrid 177"
+ },
+ "latitude": 42.21827574,
+ "longitude": -8.679588272,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "31"
+ ]
+ },
+ {
+ "stopId": 4820,
+ "name": {
+ "original": "Estrada Vella de Madrid 31"
+ },
+ "latitude": 42.216687676,
+ "longitude": -8.693917852,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 4830,
+ "name": {
+ "original": "Estrada Vella de Madrid 81"
+ },
+ "latitude": 42.218719793,
+ "longitude": -8.690012555,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 4840,
+ "name": {
+ "original": "Estrada Vella de Madrid 76"
+ },
+ "latitude": 42.218745617,
+ "longitude": -8.689009409,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "U2",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 4850,
+ "name": {
+ "original": "Estrada Vella de Madrid (frente Centro Comercial)"
+ },
+ "latitude": 42.216448197,
+ "longitude": -8.68163748,
+ "lines": [
+ "12A",
+ "13",
+ "31"
+ ]
+ },
+ {
+ "stopId": 4860,
+ "name": {
+ "original": "Estrada da Venda (cruce Camiño da Coutadiña)"
+ },
+ "latitude": 42.182744465,
+ "longitude": -8.70286123,
+ "lines": [
+ "A",
+ "6"
+ ]
+ },
+ {
+ "stopId": 4870,
+ "name": {
+ "original": "Estrada da Venda 5"
+ },
+ "latitude": 42.192029809,
+ "longitude": -8.712623715,
+ "lines": [
+ "A",
+ "27",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 4880,
+ "name": {
+ "original": "Estrada da Venda 240"
+ },
+ "latitude": 42.188021902,
+ "longitude": -8.711457595,
+ "lines": [
+ "A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 4890,
+ "name": {
+ "original": "Estrada da Venda 238"
+ },
+ "latitude": 42.186610014,
+ "longitude": -8.710023944,
+ "lines": [
+ "A",
+ "6"
+ ]
+ },
+ {
+ "stopId": 4900,
+ "name": {
+ "original": "Estrada da Venda 35"
+ },
+ "latitude": 42.188304845,
+ "longitude": -8.711474749,
+ "lines": [
+ "A",
+ "27",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 4910,
+ "name": {
+ "original": "Estrada da Venda 4"
+ },
+ "latitude": 42.19205763,
+ "longitude": -8.712722956,
+ "lines": [
+ "A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 4920,
+ "name": {
+ "original": "Estrada da Venda 49"
+ },
+ "latitude": 42.186610342,
+ "longitude": -8.709937804,
+ "lines": [
+ "A",
+ "6"
+ ]
+ },
+ {
+ "stopId": 4930,
+ "name": {
+ "original": "Travesía da Devesa (Asociación Veciños)"
+ },
+ "latitude": 42.246018558,
+ "longitude": -8.669287042,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 4940,
+ "name": {
+ "original": "Camiño da Devesa (fronte 55)"
+ },
+ "latitude": 42.245450719,
+ "longitude": -8.672406783,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 4960,
+ "name": {
+ "original": "Rúa do Doutor Corbal 135"
+ },
+ "latitude": 42.256210875,
+ "longitude": -8.696882723,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 4970,
+ "name": {
+ "original": "Rúa do Doutor Corbal 126"
+ },
+ "latitude": 42.255883312,
+ "longitude": -8.696812986,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 4980,
+ "name": {
+ "original": "Rúa do Doutor Corbal 149"
+ },
+ "latitude": 42.257539061,
+ "longitude": -8.697019814,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 5000,
+ "name": {
+ "original": "Rúa do Doutor Corbal 6"
+ },
+ "latitude": 42.250121395,
+ "longitude": -8.696122376,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 5010,
+ "name": {
+ "original": "Rúa do Doutor Corbal 95"
+ },
+ "latitude": 42.254169539,
+ "longitude": -8.697752273,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 5020,
+ "name": {
+ "original": "Rúa do Doutor Corbal 94"
+ },
+ "latitude": 42.254104511,
+ "longitude": -8.697655199,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 5030,
+ "name": {
+ "original": "Avda. de Redondela (fronte 28)"
+ },
+ "latitude": 42.272502528,
+ "longitude": -8.664787252,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 5040,
+ "name": {
+ "original": "Rúa da Salgueira Entrada 5"
+ },
+ "latitude": 42.222543586,
+ "longitude": -8.717809812,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 5060,
+ "name": {
+ "original": "Estadio de Balaídos (Avda. do Fragoso)"
+ },
+ "latitude": 42.212676247,
+ "longitude": -8.739585545,
+ "lines": [
+ "16",
+ "23",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 5070,
+ "name": {
+ "original": "Rúa da Estrada 10-12"
+ },
+ "latitude": 42.220535836,
+ "longitude": -8.74141599,
+ "lines": [
+ "C3i",
+ "5B"
+ ]
+ },
+ {
+ "stopId": 5090,
+ "name": {
+ "original": "Rúa da Estrada 1"
+ },
+ "latitude": 42.220359068,
+ "longitude": -8.738861999,
+ "lines": [
+ "C3i",
+ "5B"
+ ]
+ },
+ {
+ "stopId": 5120,
+ "name": {
+ "original": "Camiño da Falcoa 41"
+ },
+ "latitude": 42.204935234,
+ "longitude": -8.722453969,
+ "lines": [
+ "18B",
+ "18H",
+ "27"
+ ]
+ },
+ {
+ "stopId": 5140,
+ "name": {
+ "original": "Camiño da Falcoa 15"
+ },
+ "latitude": 42.207336295,
+ "longitude": -8.72129506,
+ "lines": [
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 5160,
+ "name": {
+ "original": "Camiño da Falcoa 26"
+ },
+ "latitude": 42.204783634,
+ "longitude": -8.722664062,
+ "lines": [
+ "18B",
+ "18H",
+ "27"
+ ]
+ },
+ {
+ "stopId": 5170,
+ "name": {
+ "original": "Rúa das Figueiras (Praza da Feira)"
+ },
+ "latitude": 42.227529644,
+ "longitude": -8.66169041,
+ "lines": [
+ "15A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 5180,
+ "name": {
+ "original": "Rúa das Figueiras 96"
+ },
+ "latitude": 42.224562288,
+ "longitude": -8.66579419,
+ "lines": [
+ "15A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 5190,
+ "name": {
+ "original": "Rúa das Figueiras 124"
+ },
+ "latitude": 42.226051942,
+ "longitude": -8.663629647,
+ "lines": [
+ "15A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 5200,
+ "name": {
+ "original": "Rúa das Figueiras 138"
+ },
+ "latitude": 42.227533616,
+ "longitude": -8.661561664,
+ "lines": [
+ "15A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 5220,
+ "name": {
+ "original": "Rúa das Figueiras 190"
+ },
+ "latitude": 42.228984217,
+ "longitude": -8.658438326,
+ "lines": [
+ "15A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 5230,
+ "name": {
+ "original": "Rúa das Figueiras 254"
+ },
+ "latitude": 42.230261894,
+ "longitude": -8.653683012,
+ "lines": [
+ "15A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 5250,
+ "name": {
+ "original": "Rúa das Figueiras 29"
+ },
+ "latitude": 42.223048763,
+ "longitude": -8.667374011,
+ "lines": [
+ "15A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 5260,
+ "name": {
+ "original": "Rúa das Figueiras 22"
+ },
+ "latitude": 42.222838218,
+ "longitude": -8.667795118,
+ "lines": [
+ "15A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 5270,
+ "name": {
+ "original": "Rúa das Figueiras 65"
+ },
+ "latitude": 42.224367639,
+ "longitude": -8.665989991,
+ "lines": [
+ "15A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 5280,
+ "name": {
+ "original": "Rúa das Figueiras 113"
+ },
+ "latitude": 42.225940716,
+ "longitude": -8.663830813,
+ "lines": [
+ "15A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 5290,
+ "name": {
+ "original": "Avda. da Florida 60"
+ },
+ "latitude": 42.215060936,
+ "longitude": -8.741309568,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 5300,
+ "name": {
+ "original": "Avda. da Florida 117"
+ },
+ "latitude": 42.214827343,
+ "longitude": -8.741319027,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 5310,
+ "name": {
+ "original": "Avda. da Florida 140"
+ },
+ "latitude": 42.208024308,
+ "longitude": -8.750412985,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 5320,
+ "name": {
+ "original": "Avda. da Florida 145"
+ },
+ "latitude": 42.213244058,
+ "longitude": -8.743657913,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 5330,
+ "name": {
+ "original": "Avda. da Florida 3"
+ },
+ "latitude": 42.220228436,
+ "longitude": -8.733426296,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 5340,
+ "name": {
+ "original": "Avda. da Florida 197"
+ },
+ "latitude": 42.208621114,
+ "longitude": -8.749572184,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 5350,
+ "name": {
+ "original": "Avda. da Florida 40"
+ },
+ "latitude": 42.216779248,
+ "longitude": -8.738705143,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 5360,
+ "name": {
+ "original": "Avda. da Florida 70"
+ },
+ "latitude": 42.212804215,
+ "longitude": -8.744506761,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 5370,
+ "name": {
+ "original": "Avda. da Florida 69"
+ },
+ "latitude": 42.216928527,
+ "longitude": -8.738268035,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 5380,
+ "name": {
+ "original": "Avda. da Florida 8"
+ },
+ "latitude": 42.219743053,
+ "longitude": -8.734322187,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 5390,
+ "name": {
+ "original": "Estrada de Fragoselo (cruce Camiño Río da Barxa)"
+ },
+ "latitude": 42.179556959,
+ "longitude": -8.761854527,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 5400,
+ "name": {
+ "original": "Avda. do Fragoso 95"
+ },
+ "latitude": 42.213518168,
+ "longitude": -8.737719785,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 5410,
+ "name": {
+ "original": "Avda. do Fragoso 12"
+ },
+ "latitude": 42.219689698,
+ "longitude": -8.733384686,
+ "lines": [
+ "A",
+ "16",
+ "23",
+ "N4",
+ "U1",
+ "H1",
+ "H",
+ "LZH",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5420,
+ "name": {
+ "original": "Avda. do Fragoso 3"
+ },
+ "latitude": 42.220297406,
+ "longitude": -8.732924981,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 5430,
+ "name": {
+ "original": "Avda. do Fragoso 36"
+ },
+ "latitude": 42.217381871,
+ "longitude": -8.734669814,
+ "lines": [
+ "A",
+ "16",
+ "23",
+ "N4",
+ "U1",
+ "H1",
+ "H",
+ "LZH",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5440,
+ "name": {
+ "original": "Avda. do Fragoso 47"
+ },
+ "latitude": 42.217288508,
+ "longitude": -8.73454375,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 5450,
+ "name": {
+ "original": "Avda. do Fragoso 54"
+ },
+ "latitude": 42.215348678,
+ "longitude": -8.736273638,
+ "lines": [
+ "A",
+ "16",
+ "23",
+ "N4",
+ "U1",
+ "H1",
+ "H",
+ "LZH",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5460,
+ "name": {
+ "original": "Avda. do Fragoso 71"
+ },
+ "latitude": 42.215301002,
+ "longitude": -8.736123434,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 5470,
+ "name": {
+ "original": "Avda. do Fragoso 86"
+ },
+ "latitude": 42.21315264,
+ "longitude": -8.738470803,
+ "lines": [
+ "A",
+ "16",
+ "23",
+ "N4",
+ "U1",
+ "H1",
+ "H",
+ "LZH",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5480,
+ "name": {
+ "original": "Estrada de San Xoán 25"
+ },
+ "latitude": 42.186179757,
+ "longitude": -8.748888329,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 5490,
+ "name": {
+ "original": "Avda. de García Barbón (fronte 120)"
+ },
+ "latitude": 42.241557555,
+ "longitude": -8.707094861,
+ "lines": [
+ "C3i",
+ "5B",
+ "10",
+ "17",
+ "N1",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 5500,
+ "name": {
+ "original": "Avda. de García Barbón 126"
+ },
+ "latitude": 42.24178025,
+ "longitude": -8.706720936,
+ "lines": [
+ "C3d",
+ "5B",
+ "10",
+ "17",
+ "31",
+ "U2",
+ "H2",
+ "H3",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5510,
+ "name": {
+ "original": "Avda. de García Barbón 127"
+ },
+ "latitude": 42.238290358,
+ "longitude": -8.71014103,
+ "lines": [
+ "C3i",
+ "5B",
+ "10",
+ "17",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 5520,
+ "name": {
+ "original": "Avda. de García Barbón 7"
+ },
+ "latitude": 42.237485586,
+ "longitude": -8.719397801,
+ "lines": [
+ "C3i",
+ "A",
+ "4A",
+ "4C",
+ "5B",
+ "6",
+ "7",
+ "9B",
+ "10",
+ "12B",
+ "14",
+ "16",
+ "17",
+ "18A",
+ "18B",
+ "18H",
+ "28",
+ "N1",
+ "N4",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 5530,
+ "name": {
+ "original": "Avda. de García Barbón 18"
+ },
+ "latitude": 42.23720456,
+ "longitude": -8.718680736,
+ "lines": [
+ "C3d",
+ "5B",
+ "10",
+ "17",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5540,
+ "name": {
+ "original": "Avda. de García Barbón 28"
+ },
+ "latitude": 42.236759685,
+ "longitude": -8.716384581,
+ "lines": [
+ "C3d",
+ "A",
+ "5B",
+ "10",
+ "16",
+ "17",
+ "24",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5560,
+ "name": {
+ "original": "Avda. de García Barbón 60"
+ },
+ "latitude": 42.236896919,
+ "longitude": -8.712902905,
+ "lines": [
+ "C3d",
+ "A",
+ "5B",
+ "10",
+ "16",
+ "17",
+ "24",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5570,
+ "name": {
+ "original": "Avda. de García Barbón 87"
+ },
+ "latitude": 42.237003397,
+ "longitude": -8.713586994,
+ "lines": [
+ "C3i",
+ "5B",
+ "10",
+ "17",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 5580,
+ "name": {
+ "original": "Avda. de García Barbón 90"
+ },
+ "latitude": 42.238267348,
+ "longitude": -8.709873166,
+ "lines": [
+ "C3d",
+ "5B",
+ "10",
+ "17",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5590,
+ "name": {
+ "original": "Avda. da Ponte (Grupo S. Gorxal)"
+ },
+ "latitude": 42.213057463,
+ "longitude": -8.670479203,
+ "lines": [
+ "12B",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 5600,
+ "name": {
+ "original": "Avda. da Gran Vía 107"
+ },
+ "latitude": 42.225140059,
+ "longitude": -8.72184818,
+ "lines": [
+ "C3i",
+ "7",
+ "11",
+ "13",
+ "15A",
+ "16",
+ "23",
+ "29",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 5610,
+ "name": {
+ "original": "Avda. da Gran Vía 12"
+ },
+ "latitude": 42.233503531,
+ "longitude": -8.717236739,
+ "lines": [
+ "C1",
+ "4A",
+ "4C",
+ "5B",
+ "7",
+ "12B",
+ "14",
+ "16",
+ "17",
+ "18A",
+ "18B",
+ "18H",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 5620,
+ "name": {
+ "original": "Avda. da Gran Vía 148"
+ },
+ "latitude": 42.222321607,
+ "longitude": -8.726440421,
+ "lines": [
+ "C3d",
+ "13",
+ "15A",
+ "23",
+ "29",
+ "U1",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5630,
+ "name": {
+ "original": "Avda. da Gran Vía 147"
+ },
+ "latitude": 42.222790356,
+ "longitude": -8.724940076,
+ "lines": [
+ "C3i",
+ "7",
+ "11",
+ "13",
+ "15A",
+ "16",
+ "23",
+ "29",
+ "U2",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 5640,
+ "name": {
+ "original": "Avda. da Gran Vía 176"
+ },
+ "latitude": 42.220868033,
+ "longitude": -8.730264447,
+ "lines": [
+ "C3d",
+ "13",
+ "15A",
+ "23",
+ "29",
+ "U2",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5650,
+ "name": {
+ "original": "Avda. da Gran Vía 185"
+ },
+ "latitude": 42.220971136,
+ "longitude": -8.729196245,
+ "lines": [
+ "C3i",
+ "7",
+ "11",
+ "13",
+ "15A",
+ "16",
+ "23",
+ "29",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 5660,
+ "name": {
+ "original": "Avda. da Gran Vía 19"
+ },
+ "latitude": 42.232957918,
+ "longitude": -8.717215735,
+ "lines": [
+ "4A",
+ "4C",
+ "5B",
+ "7",
+ "11",
+ "12B",
+ "14",
+ "16",
+ "17",
+ "18A",
+ "18B",
+ "18H",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 5670,
+ "name": {
+ "original": "Avda. da Gran Vía 46"
+ },
+ "latitude": 42.230988893,
+ "longitude": -8.71867283,
+ "lines": [
+ "C1",
+ "12A",
+ "12B",
+ "14",
+ "18A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 5680,
+ "name": {
+ "original": "Avda. da Gran Vía 66"
+ },
+ "latitude": 42.228173802,
+ "longitude": -8.720267623,
+ "lines": [
+ "C3d",
+ "13",
+ "15A",
+ "23",
+ "29",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5690,
+ "name": {
+ "original": "Avda. da Gran Vía 85"
+ },
+ "latitude": 42.226868266,
+ "longitude": -8.720637433,
+ "lines": [
+ "C3i",
+ "7",
+ "11",
+ "13",
+ "15A",
+ "16",
+ "23",
+ "29",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 5700,
+ "name": {
+ "original": "Avda. da Gran Vía 104"
+ },
+ "latitude": 42.225325884,
+ "longitude": -8.722106624,
+ "lines": [
+ "C3d",
+ "13",
+ "15A",
+ "23",
+ "29",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 5710,
+ "name": {
+ "original": "Avda. do Alcalde Gregorio Espino 22"
+ },
+ "latitude": 42.23003666347398,
+ "longitude": -8.707266671978003,
+ "lines": [
+ "31"
+ ]
+ },
+ {
+ "stopId": 5720,
+ "name": {
+ "original": "Avda. do Alcalde Gregorio Espino 33"
+ },
+ "latitude": 42.23004933454558,
+ "longitude": -8.706947409683313,
+ "lines": [
+ "4C",
+ "23",
+ "31",
+ "H2",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 5730,
+ "name": {
+ "original": "Avda. do Alcalde Gregorio Espino 44"
+ },
+ "latitude": 42.227850036119314,
+ "longitude": -8.708105429626789,
+ "lines": [
+ "31",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 5740,
+ "name": {
+ "original": "Avda. do Alcalde Gregorio Espino 57"
+ },
+ "latitude": 42.22783722597372,
+ "longitude": -8.707849091551859,
+ "lines": [
+ "4C",
+ "23",
+ "31",
+ "N4",
+ "H2",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 5750,
+ "name": {
+ "original": "Avda. do Alcalde Gregorio Espino 79"
+ },
+ "latitude": 42.225785485,
+ "longitude": -8.708786105,
+ "lines": [
+ "4C",
+ "23",
+ "31",
+ "N4",
+ "H2",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 5760,
+ "name": {
+ "original": "Estrada de Valadares 377"
+ },
+ "latitude": 42.16929549,
+ "longitude": -8.723249687,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 5770,
+ "name": {
+ "original": "Estrada de Valadares 310"
+ },
+ "latitude": 42.16911061,
+ "longitude": -8.723904146,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 5790,
+ "name": {
+ "original": "Hospital do Meixoeiro"
+ },
+ "latitude": 42.214534062,
+ "longitude": -8.684756053,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "15B",
+ "15C",
+ "31"
+ ]
+ },
+ {
+ "stopId": 5800,
+ "name": {
+ "original": "Rúa de Jenaro de la Fuente 29"
+ },
+ "latitude": 42.232202275,
+ "longitude": -8.703792246,
+ "lines": [
+ "A",
+ "4A",
+ "6",
+ "9B",
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "24",
+ "25",
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 5810,
+ "name": {
+ "original": "Rúa de Jenaro de la Fuente 10"
+ },
+ "latitude": 42.232258514,
+ "longitude": -8.705770621,
+ "lines": [
+ "A",
+ "4A",
+ "4C",
+ "6",
+ "9B",
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "23",
+ "24",
+ "25",
+ "27",
+ "28",
+ "N4",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 5820,
+ "name": {
+ "original": "Rúa de Jenaro de la Fuente 22"
+ },
+ "latitude": 42.232042043,
+ "longitude": -8.703616807,
+ "lines": [
+ "A",
+ "4A",
+ "4C",
+ "6",
+ "9B",
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "23",
+ "24",
+ "25",
+ "27",
+ "28",
+ "N4",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 5830,
+ "name": {
+ "original": "Rúa de Jenaro de la Fuente 11"
+ },
+ "latitude": 42.23238699,
+ "longitude": -8.705610784,
+ "lines": [
+ "A",
+ "4A",
+ "6",
+ "9B",
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "24",
+ "25",
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 5840,
+ "name": {
+ "original": "Rúa da Lagoa (cruce Rúa do Balde)"
+ },
+ "latitude": 42.201360896,
+ "longitude": -8.702877394,
+ "lines": [
+ "18A",
+ "18B"
+ ]
+ },
+ {
+ "stopId": 5850,
+ "name": {
+ "original": "Rúa da Lagoa (fronte 21)"
+ },
+ "latitude": 42.201154253,
+ "longitude": -8.702343635,
+ "lines": [
+ "18A",
+ "18B"
+ ]
+ },
+ {
+ "stopId": 5860,
+ "name": {
+ "original": "Baixada á Laxe (Centro Saúde)"
+ },
+ "latitude": 42.21669522,
+ "longitude": -8.715884298,
+ "lines": [
+ "18A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 5870,
+ "name": {
+ "original": "Rúa de López Mora 10"
+ },
+ "latitude": 42.226432697,
+ "longitude": -8.731262013,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 5880,
+ "name": {
+ "original": "Rúa de López Mora 19"
+ },
+ "latitude": 42.226800566,
+ "longitude": -8.731799623,
+ "lines": [
+ "5A",
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 5890,
+ "name": {
+ "original": "Rúa de López Mora 84"
+ },
+ "latitude": 42.222420533,
+ "longitude": -8.731996938,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 5900,
+ "name": {
+ "original": "Rúa de Macal 8"
+ },
+ "latitude": 42.202071034,
+ "longitude": -8.723541884,
+ "lines": [
+ "18B",
+ "18H",
+ "27"
+ ]
+ },
+ {
+ "stopId": 5910,
+ "name": {
+ "original": "Rúa de Macal 90"
+ },
+ "latitude": 42.196213336,
+ "longitude": -8.721136467,
+ "lines": [
+ "18B",
+ "18H",
+ "27"
+ ]
+ },
+ {
+ "stopId": 5920,
+ "name": {
+ "original": "Rúa de Macal (cruce Rúa de Ramiro Pascual)"
+ },
+ "latitude": 42.196173594,
+ "longitude": -8.721023814,
+ "lines": [
+ "18B",
+ "18H",
+ "27"
+ ]
+ },
+ {
+ "stopId": 5930,
+ "name": {
+ "original": "Rúa de Macal (frente 8)"
+ },
+ "latitude": 42.202050775,
+ "longitude": -8.723454133,
+ "lines": [
+ "18B",
+ "18H",
+ "27"
+ ]
+ },
+ {
+ "stopId": 5940,
+ "name": {
+ "original": "Rúa de Macal 39"
+ },
+ "latitude": 42.198347336,
+ "longitude": -8.721488003,
+ "lines": [
+ "18B",
+ "18H",
+ "27"
+ ]
+ },
+ {
+ "stopId": 5950,
+ "name": {
+ "original": "Rúa dos Mestres Goldar 62"
+ },
+ "latitude": 42.207127325,
+ "longitude": -8.726636888,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 5960,
+ "name": {
+ "original": "Rúa dos Mestres Goldar 77"
+ },
+ "latitude": 42.206757786,
+ "longitude": -8.730056705,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 5970,
+ "name": {
+ "original": "Rúa dos Mestres Goldar 96"
+ },
+ "latitude": 42.206880283,
+ "longitude": -8.729978952,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 5980,
+ "name": {
+ "original": "Rúa dos Mestres Goldar 37"
+ },
+ "latitude": 42.207198849,
+ "longitude": -8.726658346,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 6000,
+ "name": {
+ "original": "Rúa de Manuel Álvarez 185"
+ },
+ "latitude": 42.220887532,
+ "longitude": -8.685059571,
+ "lines": [
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 6010,
+ "name": {
+ "original": "Rúa de Manuel Álvarez (cruce Camiño Sulevada)"
+ },
+ "latitude": 42.223077231,
+ "longitude": -8.675499751,
+ "lines": [
+ "25",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 6020,
+ "name": {
+ "original": "Rúa de Manuel Álvarez 58"
+ },
+ "latitude": 42.223195307,
+ "longitude": -8.681872932,
+ "lines": [
+ "25",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 6030,
+ "name": {
+ "original": "Rúa de Manuel Álvarez 102"
+ },
+ "latitude": 42.220829925,
+ "longitude": -8.685158812,
+ "lines": [
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 6040,
+ "name": {
+ "original": "Rúa de Manuel Álvarez (fronte cruce Camiño Sulevada)"
+ },
+ "latitude": 42.22320325,
+ "longitude": -8.675213007,
+ "lines": [
+ "25",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 6050,
+ "name": {
+ "original": "Rúa de Manuel Cominges 22"
+ },
+ "latitude": 42.19890849,
+ "longitude": -8.73623433,
+ "lines": [
+ "12B",
+ "17"
+ ]
+ },
+ {
+ "stopId": 6060,
+ "name": {
+ "original": "Rúa de Manuel Cominges 135"
+ },
+ "latitude": 42.196716802,
+ "longitude": -8.729966982,
+ "lines": [
+ "12B"
+ ]
+ },
+ {
+ "stopId": 6070,
+ "name": {
+ "original": "Rúa de Manuel Cominges 15"
+ },
+ "latitude": 42.199197066,
+ "longitude": -8.736588816,
+ "lines": [
+ "12B"
+ ]
+ },
+ {
+ "stopId": 6080,
+ "name": {
+ "original": "Rúa de Manuel Cominges 64"
+ },
+ "latitude": 42.197736144,
+ "longitude": -8.732267294,
+ "lines": [
+ "12B",
+ "17"
+ ]
+ },
+ {
+ "stopId": 6090,
+ "name": {
+ "original": "Rúa de Manuel Cominges 77"
+ },
+ "latitude": 42.197626151,
+ "longitude": -8.732546841,
+ "lines": [
+ "12B"
+ ]
+ },
+ {
+ "stopId": 6100,
+ "name": {
+ "original": "Rúa de Manuel Cominges 80"
+ },
+ "latitude": 42.197066521,
+ "longitude": -8.730468609,
+ "lines": [
+ "12B",
+ "17"
+ ]
+ },
+ {
+ "stopId": 6110,
+ "name": {
+ "original": "Camiño da Devesa 6"
+ },
+ "latitude": 42.243941636,
+ "longitude": -8.669169025,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 6130,
+ "name": {
+ "original": "Estrada do Marco 105"
+ },
+ "latitude": 42.207132591,
+ "longitude": -8.706967295,
+ "lines": [
+ "18B",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 6140,
+ "name": {
+ "original": "Estrada do Marco (Colexio)"
+ },
+ "latitude": 42.206925967,
+ "longitude": -8.707050443,
+ "lines": [
+ "18B",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 6150,
+ "name": {
+ "original": "Estrada De Zamáns 233"
+ },
+ "latitude": 42.160616846,
+ "longitude": -8.691088401,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 6160,
+ "name": {
+ "original": "Estrada De Zamáns 150"
+ },
+ "latitude": 42.160635942,
+ "longitude": -8.69145083,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 6180,
+ "name": {
+ "original": "Rúa de Marín (fronte 30)"
+ },
+ "latitude": 42.21850454,
+ "longitude": -8.753892634,
+ "lines": [
+ "C3i",
+ "5B"
+ ]
+ },
+ {
+ "stopId": 6187,
+ "name": {
+ "original": "Rúa de Martín Echegaray (Parque)"
+ },
+ "latitude": 42.215244712,
+ "longitude": -8.74244225,
+ "lines": [
+ "23",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 6200,
+ "name": {
+ "original": "Avda. de E. Martínez Garrido 11"
+ },
+ "latitude": 42.229298043,
+ "longitude": -8.699760249,
+ "lines": [
+ "6",
+ "25",
+ "31"
+ ]
+ },
+ {
+ "stopId": 6210,
+ "name": {
+ "original": "Avda. de E. Martínez Garrido 16"
+ },
+ "latitude": 42.229259529,
+ "longitude": -8.699964485,
+ "lines": [
+ "4C",
+ "6",
+ "23",
+ "25",
+ "31",
+ "N4",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 6220,
+ "name": {
+ "original": "Avda. de E. Martínez Garrido 45"
+ },
+ "latitude": 42.226116269,
+ "longitude": -8.703346362,
+ "lines": [
+ "31"
+ ]
+ },
+ {
+ "stopId": 6230,
+ "name": {
+ "original": "Avda. de E. Martínez Garrido 69"
+ },
+ "latitude": 42.224839145,
+ "longitude": -8.706082215,
+ "lines": [
+ "31"
+ ]
+ },
+ {
+ "stopId": 6240,
+ "name": {
+ "original": "Avda. de E. Martínez Garrido 77"
+ },
+ "latitude": 42.224493844,
+ "longitude": -8.707734948,
+ "lines": [
+ "31"
+ ]
+ },
+ {
+ "stopId": 6250,
+ "name": {
+ "original": "Avda. de E. Martínez Garrido 80"
+ },
+ "latitude": 42.22667463,
+ "longitude": -8.702581423,
+ "lines": [
+ "4C",
+ "23",
+ "31",
+ "N4",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 6260,
+ "name": {
+ "original": "Estrada da Balsa 3"
+ },
+ "latitude": 42.199568643,
+ "longitude": -8.741030554,
+ "lines": [
+ "12B",
+ "17"
+ ]
+ },
+ {
+ "stopId": 6280,
+ "name": {
+ "original": "Rúa Molais (Parque da Grileira)"
+ },
+ "latitude": 42.223843943,
+ "longitude": -8.65334865,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 6290,
+ "name": {
+ "original": "Rúa de San Paio (cruce Rúa Muíños)"
+ },
+ "latitude": 42.202031781,
+ "longitude": -8.769502424,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 6300,
+ "name": {
+ "original": "Rúa de Cánovas del Castillo (Centro Comercial)"
+ },
+ "latitude": 42.240129541,
+ "longitude": -8.727159759,
+ "lines": [
+ "C3i",
+ "A",
+ "5A",
+ "5B",
+ "6",
+ "9B",
+ "10",
+ "11",
+ "15B",
+ "15C",
+ "28",
+ "N1",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 6360,
+ "name": {
+ "original": "Rúa de Pablo Iglesias (Colexio)"
+ },
+ "latitude": 42.211426266,
+ "longitude": -8.743166543,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 6370,
+ "name": {
+ "original": "Rúa de Pablo Iglesias (Río)"
+ },
+ "latitude": 42.210130966,
+ "longitude": -8.744872428,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 6380,
+ "name": {
+ "original": "Rúa de Pablo Iglesias 2"
+ },
+ "latitude": 42.211811672,
+ "longitude": -8.742420889,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 6390,
+ "name": {
+ "original": "Rúa de Pablo Iglesias 20"
+ },
+ "latitude": 42.210204472,
+ "longitude": -8.74559126,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 6440,
+ "name": {
+ "original": "Camiño do Pino Manso 6"
+ },
+ "latitude": 42.222951208,
+ "longitude": -8.635051581,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 6450,
+ "name": {
+ "original": "Praza de Suárez Llanos"
+ },
+ "latitude": 42.224306065,
+ "longitude": -8.753090783,
+ "lines": [
+ "C3d",
+ "C3i",
+ "13",
+ "U1",
+ "H"
+ ]
+ },
+ {
+ "stopId": 6460,
+ "name": {
+ "original": "Rúa dos Pescadores 10"
+ },
+ "latitude": 42.224853579,
+ "longitude": -8.752608542,
+ "lines": [
+ "C3d",
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 6470,
+ "name": {
+ "original": "Rúa de Pi i Margall 108"
+ },
+ "latitude": 42.228458938,
+ "longitude": -8.732202674,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 6480,
+ "name": {
+ "original": "Rúa de Pi i Margall 137"
+ },
+ "latitude": 42.229149188,
+ "longitude": -8.731886908,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 6490,
+ "name": {
+ "original": "Rúa de Pi i Margall 32"
+ },
+ "latitude": 42.233972514,
+ "longitude": -8.729963004,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 6500,
+ "name": {
+ "original": "Rúa de Pi i Margall 51"
+ },
+ "latitude": 42.233690068,
+ "longitude": -8.730100174,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 6510,
+ "name": {
+ "original": "Rúa de Pi i Margall 86"
+ },
+ "latitude": 42.230584487,
+ "longitude": -8.731459155,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 6520,
+ "name": {
+ "original": "Rúa de Pi i Margall 95"
+ },
+ "latitude": 42.232285981,
+ "longitude": -8.730816324,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 6530,
+ "name": {
+ "original": "Rúa do Pintor Laxeiro 4"
+ },
+ "latitude": 42.222480852,
+ "longitude": -8.729812967,
+ "lines": [
+ "C1"
+ ]
+ },
+ {
+ "stopId": 6550,
+ "name": {
+ "original": "Rúa de Pizarro 10"
+ },
+ "latitude": 42.229886416,
+ "longitude": -8.717463365,
+ "lines": [
+ "C3i",
+ "6",
+ "11",
+ "15A",
+ "23",
+ "25",
+ "28",
+ "29"
+ ]
+ },
+ {
+ "stopId": 6560,
+ "name": {
+ "original": "Rúa de Pizarro 34"
+ },
+ "latitude": 42.23113801,
+ "longitude": -8.711696824,
+ "lines": [
+ "C3i",
+ "6",
+ "11",
+ "15A",
+ "23",
+ "25",
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 6570,
+ "name": {
+ "original": "Rúa de Pizarro 65"
+ },
+ "latitude": 42.231357755,
+ "longitude": -8.71296778,
+ "lines": [
+ "C3d",
+ "6",
+ "15A",
+ "23",
+ "25",
+ "27",
+ "28",
+ "U2",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 6580,
+ "name": {
+ "original": "Rúa de Pizarro 7"
+ },
+ "latitude": 42.229860487,
+ "longitude": -8.717979137,
+ "lines": [
+ "C3d",
+ "6",
+ "15A",
+ "23",
+ "25",
+ "27",
+ "28",
+ "U2",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 6620,
+ "name": {
+ "original": "Rúa de Policarpo Sanz 40"
+ },
+ "latitude": 42.23757846151978,
+ "longitude": -8.721031378896738,
+ "lines": [
+ "C1",
+ "A",
+ "5A",
+ "9B",
+ "15B",
+ "15C",
+ "24",
+ "28",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 6640,
+ "name": {
+ "original": "Rúa do Porriño (fronte Instituto)"
+ },
+ "latitude": 42.215506175,
+ "longitude": -8.753186569,
+ "lines": [
+ "C3d",
+ "C3i",
+ "4A",
+ "4C",
+ "15A",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 6650,
+ "name": {
+ "original": "Rúa do Porriño (Instituto)"
+ },
+ "latitude": 42.215554055,
+ "longitude": -8.753042462,
+ "lines": [
+ "C3d",
+ "C3i",
+ "5B",
+ "15B",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 6670,
+ "name": {
+ "original": "Estrada da Venda 109"
+ },
+ "latitude": 42.183004832,
+ "longitude": -8.702601056,
+ "lines": [
+ "A",
+ "6"
+ ]
+ },
+ {
+ "stopId": 6680,
+ "name": {
+ "original": "Rúa do Portoloureiro 26"
+ },
+ "latitude": 42.207311837,
+ "longitude": -8.718650635,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 6690,
+ "name": {
+ "original": "Rúa do Portoloureiro 52"
+ },
+ "latitude": 42.20495956,
+ "longitude": -8.715695557,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 6700,
+ "name": {
+ "original": "Rúa do Portoloureiro 43"
+ },
+ "latitude": 42.204947406,
+ "longitude": -8.715589643,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 6720,
+ "name": {
+ "original": "Rúa do Portoloureiro (fronte 28)"
+ },
+ "latitude": 42.207385347,
+ "longitude": -8.718127604,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 6730,
+ "name": {
+ "original": "Rúa do Couto 29"
+ },
+ "latitude": 42.199386411,
+ "longitude": -8.695417016,
+ "lines": [
+ "18A",
+ "18B"
+ ]
+ },
+ {
+ "stopId": 6740,
+ "name": {
+ "original": "Rúa da Vista do Mar 95"
+ },
+ "latitude": 42.242957479,
+ "longitude": -8.691167762,
+ "lines": [
+ "4A",
+ "24"
+ ]
+ },
+ {
+ "stopId": 6750,
+ "name": {
+ "original": "Avda. de Samil (Praia da Fonte)"
+ },
+ "latitude": 42.221783554,
+ "longitude": -8.773517669,
+ "lines": [
+ "10",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 6760,
+ "name": {
+ "original": "Rúa de Canido (Praia da Calzoa)"
+ },
+ "latitude": 42.201847037,
+ "longitude": -8.782205633,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 6780,
+ "name": {
+ "original": "Avda. de Samil (Dunas)"
+ },
+ "latitude": 42.210256843,
+ "longitude": -8.774740625,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 6790,
+ "name": {
+ "original": "Avda. de Samil (fronte Hotel)"
+ },
+ "latitude": 42.214772655,
+ "longitude": -8.774772363,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 6810,
+ "name": {
+ "original": "Avda. de Samil (Parking)"
+ },
+ "latitude": 42.207405928,
+ "longitude": -8.776153122,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 6820,
+ "name": {
+ "original": "Avda. de Samil (Polideportivo)"
+ },
+ "latitude": 42.20362713,
+ "longitude": -8.777027535,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 6830,
+ "name": {
+ "original": "Praia do Vao"
+ },
+ "latitude": 42.197495083,
+ "longitude": -8.790235556,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 6860,
+ "name": {
+ "original": "Praza de Compostela"
+ },
+ "latitude": 42.239118346,
+ "longitude": -8.722531274,
+ "lines": [
+ "C3i",
+ "A",
+ "5A",
+ "5B",
+ "6",
+ "9B",
+ "10",
+ "11",
+ "15B",
+ "15C",
+ "24",
+ "28",
+ "N1",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 6880,
+ "name": {
+ "original": "Praza do Cristo da Vitoria"
+ },
+ "latitude": 42.215576278,
+ "longitude": -8.748885599,
+ "lines": [
+ "4A",
+ "4C",
+ "11",
+ "15A",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 6890,
+ "name": {
+ "original": "Rúa da Cruz 36"
+ },
+ "latitude": 42.198959642,
+ "longitude": -8.68628496,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 6900,
+ "name": {
+ "original": "Rúa da Cruz 49"
+ },
+ "latitude": 42.199031443,
+ "longitude": -8.686165797,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 6930,
+ "name": {
+ "original": "Praza de América 1"
+ },
+ "latitude": 42.220997313,
+ "longitude": -8.732835177,
+ "lines": [
+ "C1",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 6940,
+ "name": {
+ "original": "Praza de América 3"
+ },
+ "latitude": 42.220663902,
+ "longitude": -8.733419892,
+ "lines": [
+ "C3d",
+ "4A",
+ "4C",
+ "5A",
+ "5B",
+ "10",
+ "11",
+ "12A",
+ "13",
+ "15A",
+ "29",
+ "N4",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 6950,
+ "name": {
+ "original": "Praza de España (cruce Rúa de Pizarro)"
+ },
+ "latitude": 42.229280401,
+ "longitude": -8.719123549,
+ "lines": [
+ "7",
+ "12A",
+ "12B",
+ "14",
+ "16",
+ "18A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 6955,
+ "name": {
+ "original": "Praza de España (cruce Rúa de Fernando Conde)"
+ },
+ "latitude": 42.229438377,
+ "longitude": -8.719781108,
+ "lines": [
+ "6",
+ "13",
+ "18B",
+ "18H",
+ "25",
+ "29"
+ ]
+ },
+ {
+ "stopId": 6960,
+ "name": {
+ "original": "Avda. das Camelias s/n ( Praza do Rei)"
+ },
+ "latitude": 42.235056275,
+ "longitude": -8.726757514,
+ "lines": [
+ "4A",
+ "4C",
+ "7",
+ "12B",
+ "16",
+ "17",
+ "27",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 6970,
+ "name": {
+ "original": "Rúa da Coruña 52"
+ },
+ "latitude": 42.223781999,
+ "longitude": -8.735258991,
+ "lines": [
+ "C1",
+ "A",
+ "10",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 6980,
+ "name": {
+ "original": "Praza Eugenio Fadrique 6"
+ },
+ "latitude": 42.224471236,
+ "longitude": -8.73619635,
+ "lines": [
+ "C3i",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 6990,
+ "name": {
+ "original": "Praza Eugenio Fadrique 9"
+ },
+ "latitude": 42.224099613,
+ "longitude": -8.735838344,
+ "lines": [
+ "C3d",
+ "A",
+ "9B",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 7000,
+ "name": {
+ "original": "Praza de Fernando O Católico"
+ },
+ "latitude": 42.232427317,
+ "longitude": -8.711371073,
+ "lines": [
+ "A",
+ "4A",
+ "4C",
+ "5A",
+ "9B",
+ "11",
+ "15B",
+ "15C",
+ "24",
+ "28",
+ "N1",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 7030,
+ "name": {
+ "original": "Rúa de Manuel Castro 23"
+ },
+ "latitude": 42.212808952,
+ "longitude": -8.740022994,
+ "lines": [
+ "16",
+ "23",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 7040,
+ "name": {
+ "original": "Estrada de Miraflores 1"
+ },
+ "latitude": 42.218435348,
+ "longitude": -8.709924429,
+ "lines": [
+ "14",
+ "18A"
+ ]
+ },
+ {
+ "stopId": 7050,
+ "name": {
+ "original": "Praza de Miraflores 4"
+ },
+ "latitude": 42.218502886,
+ "longitude": -8.710133641,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7060,
+ "name": {
+ "original": "Estrada de Moledo 70"
+ },
+ "latitude": 42.210499286,
+ "longitude": -8.703983192,
+ "lines": [
+ "14",
+ "18A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7070,
+ "name": {
+ "original": "Estrada de Moledo 109"
+ },
+ "latitude": 42.210620188,
+ "longitude": -8.704177049,
+ "lines": [
+ "14",
+ "18A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7080,
+ "name": {
+ "original": "Rúa da Rabadeira 95"
+ },
+ "latitude": 42.238797018,
+ "longitude": -8.650399429,
+ "lines": [
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 7090,
+ "name": {
+ "original": "Rúa de Ramiro Pascual 108"
+ },
+ "latitude": 42.191918475,
+ "longitude": -8.706869692,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 7100,
+ "name": {
+ "original": "Rúa de Ramiro Pascual 16"
+ },
+ "latitude": 42.1952442,
+ "longitude": -8.71956212,
+ "lines": [
+ "12B",
+ "18B",
+ "18H",
+ "27"
+ ]
+ },
+ {
+ "stopId": 7110,
+ "name": {
+ "original": "Rúa de Ramiro Pascual 30"
+ },
+ "latitude": 42.195709195,
+ "longitude": -8.716896004,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 7120,
+ "name": {
+ "original": "Rúa de Ramiro Pascual 33"
+ },
+ "latitude": 42.195795442,
+ "longitude": -8.716893641,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 7130,
+ "name": {
+ "original": "Rúa de Ramiro Pascual 46"
+ },
+ "latitude": 42.19350247,
+ "longitude": -8.715219623,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 7140,
+ "name": {
+ "original": "Rúa de Ramiro Pascual 71"
+ },
+ "latitude": 42.193601829,
+ "longitude": -8.715157933,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 7150,
+ "name": {
+ "original": "Rúa de Ramiro Pascual 74"
+ },
+ "latitude": 42.192415468,
+ "longitude": -8.71184004,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 7160,
+ "name": {
+ "original": "Rúa de Ramiro Pascual 97"
+ },
+ "latitude": 42.192526527,
+ "longitude": -8.712588696,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 7170,
+ "name": {
+ "original": "Rúa de Ramiro Pascual 9"
+ },
+ "latitude": 42.195322512,
+ "longitude": -8.719460515,
+ "lines": [
+ "12B",
+ "18B",
+ "18H",
+ "27"
+ ]
+ },
+ {
+ "stopId": 7200,
+ "name": {
+ "original": "Avda. de Ramón Nieto 125"
+ },
+ "latitude": 42.231023929,
+ "longitude": -8.69459232,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7210,
+ "name": {
+ "original": "Avda. de Ramón Nieto 136"
+ },
+ "latitude": 42.229661504,
+ "longitude": -8.691416585,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7220,
+ "name": {
+ "original": "Avda. de Ramón Nieto 173"
+ },
+ "latitude": 42.229736581,
+ "longitude": -8.691879077,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7230,
+ "name": {
+ "original": "Avda. de Ramón Nieto 198"
+ },
+ "latitude": 42.229560215,
+ "longitude": -8.687854611,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7240,
+ "name": {
+ "original": "Avda. de Ramón Nieto 252"
+ },
+ "latitude": 42.229454954,
+ "longitude": -8.684539401,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7250,
+ "name": {
+ "original": "Avda. de Ramón Nieto 273"
+ },
+ "latitude": 42.229533333,
+ "longitude": -8.684593198,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7260,
+ "name": {
+ "original": "Avda. de Ramón Nieto 308"
+ },
+ "latitude": 42.22953241,
+ "longitude": -8.681347572,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7270,
+ "name": {
+ "original": "Avda. de Ramón Nieto 341"
+ },
+ "latitude": 42.229570145,
+ "longitude": -8.681232237,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7280,
+ "name": {
+ "original": "Avda. de Ramón Nieto 355"
+ },
+ "latitude": 42.228432118,
+ "longitude": -8.67827376,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7290,
+ "name": {
+ "original": "Avda. de Ramón Nieto 360"
+ },
+ "latitude": 42.228411862,
+ "longitude": -8.678489489,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7300,
+ "name": {
+ "original": "Avda. de Ramón Nieto 390"
+ },
+ "latitude": 42.223957681,
+ "longitude": -8.673451332,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7310,
+ "name": {
+ "original": "Avda. de Ramón Nieto 406"
+ },
+ "latitude": 42.223219273,
+ "longitude": -8.672272983,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "25"
+ ]
+ },
+ {
+ "stopId": 7320,
+ "name": {
+ "original": "Avda. de Ramón Nieto 475"
+ },
+ "latitude": 42.224462184,
+ "longitude": -8.673842935,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7330,
+ "name": {
+ "original": "Avda. de Ramón Nieto 50"
+ },
+ "latitude": 42.231776636,
+ "longitude": -8.697735869,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7340,
+ "name": {
+ "original": "Avda. de Ramón Nieto 503"
+ },
+ "latitude": 42.223272423,
+ "longitude": -8.67216119,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "25"
+ ]
+ },
+ {
+ "stopId": 7350,
+ "name": {
+ "original": "Avda. de Ramón Nieto 57"
+ },
+ "latitude": 42.231927571,
+ "longitude": -8.697668814,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7360,
+ "name": {
+ "original": "Avda. de Ramón Nieto 96"
+ },
+ "latitude": 42.230432101,
+ "longitude": -8.694045149,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7370,
+ "name": {
+ "original": "Avda. de Ramón Nieto 247"
+ },
+ "latitude": 42.229607881,
+ "longitude": -8.686980211,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7380,
+ "name": {
+ "original": "Camiño do Raviso 8"
+ },
+ "latitude": 42.220087856,
+ "longitude": -8.707014826,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7390,
+ "name": {
+ "original": "Camiño do Raviso 35"
+ },
+ "latitude": 42.220167311,
+ "longitude": -8.706963864,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7410,
+ "name": {
+ "original": "Rúa de Eduardo Cabello (Igrexa)"
+ },
+ "latitude": 42.226366639,
+ "longitude": -8.752928216,
+ "lines": [
+ "C3d",
+ "C3i",
+ "13",
+ "U1",
+ "H"
+ ]
+ },
+ {
+ "stopId": 7440,
+ "name": {
+ "original": "Rúa Da Cruz 18"
+ },
+ "latitude": 42.200640474,
+ "longitude": -8.684744444,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7450,
+ "name": {
+ "original": "Rúa da Cruz 2"
+ },
+ "latitude": 42.202168422,
+ "longitude": -8.68473908,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7460,
+ "name": {
+ "original": "Rúa da Cruz 46"
+ },
+ "latitude": 42.19807525,
+ "longitude": -8.684800771,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7470,
+ "name": {
+ "original": "Rúa Da Cruz 63"
+ },
+ "latitude": 42.198206824,
+ "longitude": -8.684878336,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7480,
+ "name": {
+ "original": "Rúa Da Cruz 19"
+ },
+ "latitude": 42.200729887,
+ "longitude": -8.684258964,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7490,
+ "name": {
+ "original": "Rúa do Carballal 52"
+ },
+ "latitude": 42.194091099,
+ "longitude": -8.683392611,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7500,
+ "name": {
+ "original": "Rúa do Carballal 18"
+ },
+ "latitude": 42.196388225,
+ "longitude": -8.684151676,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7540,
+ "name": {
+ "original": "Avda. do Tranvía 100"
+ },
+ "latitude": 42.226657624,
+ "longitude": -8.659447983,
+ "lines": [
+ "11",
+ "15A"
+ ]
+ },
+ {
+ "stopId": 7590,
+ "name": {
+ "original": "Avda. do Tranvía 40"
+ },
+ "latitude": 42.225201551,
+ "longitude": -8.667101684,
+ "lines": [
+ "11",
+ "15A"
+ ]
+ },
+ {
+ "stopId": 7600,
+ "name": {
+ "original": "Rúa dos Chans (cruce Subida Chans)"
+ },
+ "latitude": 42.198544554,
+ "longitude": -8.677824939,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7610,
+ "name": {
+ "original": "Rúa das Chans (Colexio)"
+ },
+ "latitude": 42.196647395,
+ "longitude": -8.677801904,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7620,
+ "name": {
+ "original": "Rúa das Chans 97"
+ },
+ "latitude": 42.189146758,
+ "longitude": -8.678708548,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7630,
+ "name": {
+ "original": "Rúa do Salgueiro 6"
+ },
+ "latitude": 42.243135911,
+ "longitude": -8.66173721,
+ "lines": [
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 7640,
+ "name": {
+ "original": "Rúa da Rabadeira 135"
+ },
+ "latitude": 42.241620781,
+ "longitude": -8.652000054,
+ "lines": [
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 7650,
+ "name": {
+ "original": "Rúa da Rabadeira 64"
+ },
+ "latitude": 42.239043121,
+ "longitude": -8.650247888,
+ "lines": [
+ "9B",
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 7660,
+ "name": {
+ "original": "Rúa da Rabadeira 104"
+ },
+ "latitude": 42.241610852,
+ "longitude": -8.651865944,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 7662,
+ "name": {
+ "original": "Rúa de Recaré 6"
+ },
+ "latitude": 42.211013462,
+ "longitude": -8.68554295,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7664,
+ "name": {
+ "original": "Rúa de Recaré (cruce Segade)"
+ },
+ "latitude": 42.211201327,
+ "longitude": -8.688838517,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7666,
+ "name": {
+ "original": "Rúa de Recaré 3"
+ },
+ "latitude": 42.210514541,
+ "longitude": -8.685349777,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7668,
+ "name": {
+ "original": "Rúa de Recaré 39"
+ },
+ "latitude": 42.211022529,
+ "longitude": -8.688318168,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7670,
+ "name": {
+ "original": "Rúa San Cristobo 6"
+ },
+ "latitude": 42.235169136,
+ "longitude": -8.671176685,
+ "lines": [
+ "9B"
+ ]
+ },
+ {
+ "stopId": 7680,
+ "name": {
+ "original": "Rúa San Cristobo 117"
+ },
+ "latitude": 42.240866745,
+ "longitude": -8.669273191,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 7690,
+ "name": {
+ "original": "Rúa San Cristobo 11"
+ },
+ "latitude": 42.235468446,
+ "longitude": -8.670649153,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 7700,
+ "name": {
+ "original": "Rúa San Cristobo 30"
+ },
+ "latitude": 42.236478263,
+ "longitude": -8.669194381,
+ "lines": [
+ "9B"
+ ]
+ },
+ {
+ "stopId": 7710,
+ "name": {
+ "original": "Rúa San Cristobo 41"
+ },
+ "latitude": 42.236825775,
+ "longitude": -8.669313413,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 7720,
+ "name": {
+ "original": "Rúa San Cristobo 80"
+ },
+ "latitude": 42.24099726,
+ "longitude": -8.669077286,
+ "lines": [
+ "9B"
+ ]
+ },
+ {
+ "stopId": 7730,
+ "name": {
+ "original": "Rúa do Salgueiro 24"
+ },
+ "latitude": 42.241580812,
+ "longitude": -8.658065611,
+ "lines": [
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 7740,
+ "name": {
+ "original": "Rúa de Salgueiro 23"
+ },
+ "latitude": 42.241658253,
+ "longitude": -8.658202403,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 7750,
+ "name": {
+ "original": "Rúa do Salgueiro 1"
+ },
+ "latitude": 42.243330503,
+ "longitude": -8.661852545,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 7760,
+ "name": {
+ "original": "Camiño das Cunchadas (cruce Rúa da Senra)"
+ },
+ "latitude": 42.193300199,
+ "longitude": -8.681021538,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7762,
+ "name": {
+ "original": "Rúa de Segade 41"
+ },
+ "latitude": 42.209575101,
+ "longitude": -8.690548833,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7764,
+ "name": {
+ "original": "Rúa de Segade 86"
+ },
+ "latitude": 42.209654568,
+ "longitude": -8.690495189,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 7810,
+ "name": {
+ "original": "Estrada Miraflores (Parque Parróco Xesús Alonso)"
+ },
+ "latitude": 42.217817959,
+ "longitude": -8.710940668,
+ "lines": [
+ "18A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 7830,
+ "name": {
+ "original": "Rúa de Saa 72"
+ },
+ "latitude": 42.20297089,
+ "longitude": -8.707945762,
+ "lines": [
+ "18B",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7840,
+ "name": {
+ "original": "Rúa da Saa do Monte 73"
+ },
+ "latitude": 42.203039989,
+ "longitude": -8.710936623,
+ "lines": [
+ "18B"
+ ]
+ },
+ {
+ "stopId": 7850,
+ "name": {
+ "original": "Rúa de Saa 57"
+ },
+ "latitude": 42.202923204,
+ "longitude": -8.708085237,
+ "lines": [
+ "18B",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 7860,
+ "name": {
+ "original": "Rúa de Severino Cobas 73"
+ },
+ "latitude": 42.225436283,
+ "longitude": -8.68893946,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 7870,
+ "name": {
+ "original": "Baixada á Praia 4"
+ },
+ "latitude": 42.166897971,
+ "longitude": -8.802204658,
+ "lines": [
+ "C3d",
+ "10",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 7880,
+ "name": {
+ "original": "Barrio da Salgueira 106"
+ },
+ "latitude": 42.222364236,
+ "longitude": -8.718898254,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 7890,
+ "name": {
+ "original": "Avda. de Cesáreo Vázquez 61"
+ },
+ "latitude": 42.180764289,
+ "longitude": -8.80256063,
+ "lines": [
+ "11",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 7900,
+ "name": {
+ "original": "Rúa de San Paio (cruce Camiño da Quintela)"
+ },
+ "latitude": 42.20492472,
+ "longitude": -8.768904292,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 7910,
+ "name": {
+ "original": "Rúa de San Paio (Igrexa)"
+ },
+ "latitude": 42.208040013,
+ "longitude": -8.767458581,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 7920,
+ "name": {
+ "original": "Rúa de San Paio (Torreiro)"
+ },
+ "latitude": 42.208926096,
+ "longitude": -8.765294038,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 7930,
+ "name": {
+ "original": "Rúa de San Paio 136"
+ },
+ "latitude": 42.208999211,
+ "longitude": -8.765346753,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 7940,
+ "name": {
+ "original": "Rúa de San Paio 220"
+ },
+ "latitude": 42.207987966,
+ "longitude": -8.767688321,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 7950,
+ "name": {
+ "original": "Rúa de San Paio 242"
+ },
+ "latitude": 42.206253649,
+ "longitude": -8.768742472,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 7960,
+ "name": {
+ "original": "Rúa de San Paio 284"
+ },
+ "latitude": 42.202111343,
+ "longitude": -8.769560881,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 7970,
+ "name": {
+ "original": "Rúa de San Paio (fronte 58)"
+ },
+ "latitude": 42.213820465,
+ "longitude": -8.760651176,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 7980,
+ "name": {
+ "original": "Rúa de San Paio 54"
+ },
+ "latitude": 42.21430717,
+ "longitude": -8.760710185,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 7990,
+ "name": {
+ "original": "Rúa de San Paio 76"
+ },
+ "latitude": 42.212612622,
+ "longitude": -8.760868435,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 8000,
+ "name": {
+ "original": "Rúa de San Paio 83"
+ },
+ "latitude": 42.212034517,
+ "longitude": -8.760916715,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 8010,
+ "name": {
+ "original": "Rúa de Sanjurjo Badía 106"
+ },
+ "latitude": 42.245018172,
+ "longitude": -8.703349646,
+ "lines": [
+ "C3d",
+ "5B",
+ "10",
+ "17",
+ "31",
+ "U2",
+ "H2",
+ "H3",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 8020,
+ "name": {
+ "original": "Rúa de Sanjurjo Badía 123"
+ },
+ "latitude": 42.246941664,
+ "longitude": -8.700376378,
+ "lines": [
+ "C3i",
+ "5B",
+ "10",
+ "N1",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 8030,
+ "name": {
+ "original": "Rúa de Sanjurjo Badía 136"
+ },
+ "latitude": 42.246056261,
+ "longitude": -8.701684825,
+ "lines": [
+ "C3d",
+ "5B",
+ "10",
+ "17",
+ "31",
+ "U2",
+ "H2",
+ "H3",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 8040,
+ "name": {
+ "original": "Rúa de Sanjurjo Badía 167"
+ },
+ "latitude": 42.24874024,
+ "longitude": -8.697546209,
+ "lines": [
+ "C3i",
+ "5B",
+ "10",
+ "N1",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 8050,
+ "name": {
+ "original": "Rúa de Sanjurjo Badía 202"
+ },
+ "latitude": 42.247765296,
+ "longitude": -8.698918203,
+ "lines": [
+ "C3d",
+ "5B",
+ "10",
+ "17",
+ "31",
+ "U2",
+ "H2",
+ "H3",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 8060,
+ "name": {
+ "original": "Rúa de Sanjurjo Badía 79"
+ },
+ "latitude": 42.244926864,
+ "longitude": -8.703642393,
+ "lines": [
+ "C3i",
+ "5B",
+ "10",
+ "N1",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 8090,
+ "name": {
+ "original": "Estrada Miraflores (Centro Saúde)"
+ },
+ "latitude": 42.216151643,
+ "longitude": -8.715616477,
+ "lines": [
+ "18A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 8100,
+ "name": {
+ "original": "Estrada da Gándara 79"
+ },
+ "latitude": 42.160735669,
+ "longitude": -8.709771124,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 8110,
+ "name": {
+ "original": "Estrada da Gándara (Seoane)"
+ },
+ "latitude": 42.160670051,
+ "longitude": -8.709878412,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 8120,
+ "name": {
+ "original": "Rúa de Severino Cobas (cruce Trav. de Santa Cristina)"
+ },
+ "latitude": 42.22493632,
+ "longitude": -8.694369092,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 8130,
+ "name": {
+ "original": "Rúa de Severino Cobas 51"
+ },
+ "latitude": 42.225485938,
+ "longitude": -8.692235895,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 8140,
+ "name": {
+ "original": "Rúa de Severino Cobas 14"
+ },
+ "latitude": 42.224657696,
+ "longitude": -8.696532794,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 8150,
+ "name": {
+ "original": "Rúa de Severino Cobas 3"
+ },
+ "latitude": 42.225076787,
+ "longitude": -8.697168477,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 8160,
+ "name": {
+ "original": "Rúa de Severino Cobas 88"
+ },
+ "latitude": 42.224884679,
+ "longitude": -8.694275214,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 8170,
+ "name": {
+ "original": "Rúa de Severino Cobas 140"
+ },
+ "latitude": 42.225428901,
+ "longitude": -8.688744499,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 8180,
+ "name": {
+ "original": "Estrada da Garrida 291"
+ },
+ "latitude": 42.173993336,
+ "longitude": -8.70329684,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 8190,
+ "name": {
+ "original": "Subida da Costa (Colina)"
+ },
+ "latitude": 42.214660701,
+ "longitude": -8.722840401,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 8200,
+ "name": {
+ "original": "Subida da Costa 21"
+ },
+ "latitude": 42.214714337,
+ "longitude": -8.723068388,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 8210,
+ "name": {
+ "original": "Subida da Costa 3"
+ },
+ "latitude": 42.214377129,
+ "longitude": -8.725964538,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 8220,
+ "name": {
+ "original": "Camiño da Corredoura 3"
+ },
+ "latitude": 42.213993258,
+ "longitude": -8.726740824,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 8230,
+ "name": {
+ "original": "Rúa dos Chans (cruce Camiño Regada)"
+ },
+ "latitude": 42.199146625,
+ "longitude": -8.676419461,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 8240,
+ "name": {
+ "original": "Subida á Madroa (Urbanización)"
+ },
+ "latitude": 42.243382567,
+ "longitude": -8.674125307,
+ "lines": [
+ "9B",
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 8250,
+ "name": {
+ "original": "Subida á Madroa 15"
+ },
+ "latitude": 42.242669731,
+ "longitude": -8.670096629,
+ "lines": [
+ "9B",
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 8282,
+ "name": {
+ "original": "Subida á Mouteira 6"
+ },
+ "latitude": 42.206854901,
+ "longitude": -8.686031058,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 8284,
+ "name": {
+ "original": "Subida á Mouteira (Parque Monte Calvario)"
+ },
+ "latitude": 42.206779403,
+ "longitude": -8.686218813,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 8290,
+ "name": {
+ "original": "Subida das Ánimas 31"
+ },
+ "latitude": 42.235646525,
+ "longitude": -8.685908988,
+ "lines": [
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 8300,
+ "name": {
+ "original": "Subida das Ánimas 32"
+ },
+ "latitude": 42.23556225,
+ "longitude": -8.686044366,
+ "lines": [
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 8330,
+ "name": {
+ "original": "Rúa de Tomás A. Alonso 86"
+ },
+ "latitude": 42.223897608,
+ "longitude": -8.740424721,
+ "lines": [
+ "C3i",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 8340,
+ "name": {
+ "original": "Rúa de Tomás A. Alonso 137"
+ },
+ "latitude": 42.223840474,
+ "longitude": -8.740432891,
+ "lines": [
+ "C3d",
+ "13",
+ "15B",
+ "15C",
+ "U1",
+ "H"
+ ]
+ },
+ {
+ "stopId": 8370,
+ "name": {
+ "original": "Rúa de Tomás A. Alonso 220"
+ },
+ "latitude": 42.223984208,
+ "longitude": -8.751326546,
+ "lines": [
+ "C3d",
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 8390,
+ "name": {
+ "original": "Rúa de Tomás A. Alonso 251"
+ },
+ "latitude": 42.223510532,
+ "longitude": -8.748903264,
+ "lines": [
+ "C3d",
+ "13",
+ "15B",
+ "15C",
+ "U1",
+ "H"
+ ]
+ },
+ {
+ "stopId": 8410,
+ "name": {
+ "original": "Rúa de Tomás Paredes (fronte 108)"
+ },
+ "latitude": 42.218006346,
+ "longitude": -8.754154367,
+ "lines": [
+ "C3d",
+ "10",
+ "15B"
+ ]
+ },
+ {
+ "stopId": 8420,
+ "name": {
+ "original": "Rúa de Tomás Paredes 114"
+ },
+ "latitude": 42.218031265,
+ "longitude": -8.754258995,
+ "lines": [
+ "C3i",
+ "10"
+ ]
+ },
+ {
+ "stopId": 8430,
+ "name": {
+ "original": "Rúa de Tomás Paredes 9"
+ },
+ "latitude": 42.221229518,
+ "longitude": -8.753411657,
+ "lines": [
+ "C3d",
+ "10",
+ "15B"
+ ]
+ },
+ {
+ "stopId": 8440,
+ "name": {
+ "original": "Rúa de Tomás Paredes 86"
+ },
+ "latitude": 42.220192578,
+ "longitude": -8.754164587,
+ "lines": [
+ "C3i",
+ "10"
+ ]
+ },
+ {
+ "stopId": 8450,
+ "name": {
+ "original": "Rúa do Conde de Torrecedeira (Parque)"
+ },
+ "latitude": 42.231511437,
+ "longitude": -8.732178,
+ "lines": [
+ "C1",
+ "C3d",
+ "A",
+ "5A",
+ "9B",
+ "15C",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 8460,
+ "name": {
+ "original": "Rúa do Conde de Torrecedeira 105"
+ },
+ "latitude": 42.227500225,
+ "longitude": -8.734096707,
+ "lines": [
+ "C1",
+ "C3d",
+ "A",
+ "9B",
+ "15C",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 8470,
+ "name": {
+ "original": "Rúa do Conde de Torrecedeira 21"
+ },
+ "latitude": 42.234106639,
+ "longitude": -8.731302569,
+ "lines": [
+ "C1",
+ "C3d",
+ "A",
+ "5A",
+ "9B",
+ "15C",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 8480,
+ "name": {
+ "original": "Rúa do Conde de Torrecedeira 81"
+ },
+ "latitude": 42.229616766,
+ "longitude": -8.732861043,
+ "lines": [
+ "C1",
+ "C3d",
+ "A",
+ "5A",
+ "9B",
+ "15C",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 8490,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 202"
+ },
+ "latitude": 42.244366441,
+ "longitude": -8.695452075,
+ "lines": [
+ "C3i",
+ "5A",
+ "N1",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 8500,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 105"
+ },
+ "latitude": 42.238455548,
+ "longitude": -8.703814812,
+ "lines": [
+ "C3d",
+ "5A",
+ "31",
+ "U2",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 8510,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 124"
+ },
+ "latitude": 42.238413145,
+ "longitude": -8.703563202,
+ "lines": [
+ "C3i",
+ "5A",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 8520,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 153"
+ },
+ "latitude": 42.241332883,
+ "longitude": -8.702059906,
+ "lines": [
+ "C3d",
+ "5A",
+ "31",
+ "U2",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 8530,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 158"
+ },
+ "latitude": 42.241101222,
+ "longitude": -8.701974032,
+ "lines": [
+ "C3i",
+ "5A",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 8540,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 193"
+ },
+ "latitude": 42.242844316,
+ "longitude": -8.698295825,
+ "lines": [
+ "C3d",
+ "5A",
+ "31",
+ "U2",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 8550,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 220"
+ },
+ "latitude": 42.246425568,
+ "longitude": -8.692950624,
+ "lines": [
+ "C3i",
+ "5A",
+ "5B",
+ "N1",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 8560,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 213"
+ },
+ "latitude": 42.24409871,
+ "longitude": -8.69614733,
+ "lines": [
+ "C3d",
+ "5A",
+ "31",
+ "U2",
+ "H2",
+ "H3",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 8570,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 239"
+ },
+ "latitude": 42.246143319,
+ "longitude": -8.69359937,
+ "lines": [
+ "C3d",
+ "5A",
+ "31",
+ "U2",
+ "H2",
+ "H3",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 8580,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 32"
+ },
+ "latitude": 42.233828086,
+ "longitude": -8.706311242,
+ "lines": [
+ "C3i",
+ "5A",
+ "31",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 8590,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 37"
+ },
+ "latitude": 42.233681224,
+ "longitude": -8.706702136,
+ "lines": [
+ "C3d",
+ "5A",
+ "31",
+ "U2",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 8600,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 71"
+ },
+ "latitude": 42.236350093,
+ "longitude": -8.70429745,
+ "lines": [
+ "C3d",
+ "5A",
+ "31",
+ "U2",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 8610,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 8"
+ },
+ "latitude": 42.232028188,
+ "longitude": -8.708203776,
+ "lines": [
+ "C3i",
+ "A",
+ "4A",
+ "4C",
+ "5A",
+ "6",
+ "9B",
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "23",
+ "24",
+ "25",
+ "27",
+ "28",
+ "N1",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 8620,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 82"
+ },
+ "latitude": 42.236261792,
+ "longitude": -8.703994979,
+ "lines": [
+ "C3i",
+ "5A",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 8630,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 7"
+ },
+ "latitude": 42.232045931,
+ "longitude": -8.708603793,
+ "lines": [
+ "C3d",
+ "A",
+ "4A",
+ "4C",
+ "5A",
+ "6",
+ "9B",
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "23",
+ "25",
+ "27",
+ "28",
+ "N4",
+ "U2",
+ "H2",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 8660,
+ "name": {
+ "original": "Química (CUVI)"
+ },
+ "latitude": 42.168290977,
+ "longitude": -8.68342947,
+ "lines": [
+ "A",
+ "15C",
+ "U1",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 8670,
+ "name": {
+ "original": "Bioloxía (CUVI)"
+ },
+ "latitude": 42.167687661,
+ "longitude": -8.685994335,
+ "lines": [
+ "A",
+ "15C",
+ "U1",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 8680,
+ "name": {
+ "original": "Económicas e Empresariais (CUVI)"
+ },
+ "latitude": 42.169603028,
+ "longitude": -8.680108895,
+ "lines": [
+ "A",
+ "15C",
+ "U1",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 8700,
+ "name": {
+ "original": "Enxeñeiros (CUVI)"
+ },
+ "latitude": 42.167963445,
+ "longitude": -8.688421342,
+ "lines": [
+ "A",
+ "15C",
+ "U1",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 8710,
+ "name": {
+ "original": "Universidade."
+ },
+ "latitude": 42.167985106,
+ "longitude": -8.688425395,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 8720,
+ "name": {
+ "original": "Humanidades (CUVI)"
+ },
+ "latitude": 42.169678809,
+ "longitude": -8.679104749,
+ "lines": [
+ "A",
+ "15C",
+ "U1",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 8721,
+ "name": {
+ "original": "Universidade.."
+ },
+ "latitude": 42.169776602,
+ "longitude": -8.678942156,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 8730,
+ "name": {
+ "original": "Telecomunicacións (CUVI) B"
+ },
+ "latitude": 42.170159671,
+ "longitude": -8.68735086,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 8740,
+ "name": {
+ "original": "Telecomunicacións (CUVI)"
+ },
+ "latitude": 42.170123888,
+ "longitude": -8.687270393,
+ "lines": [
+ "A",
+ "15C",
+ "U1",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 8750,
+ "name": {
+ "original": "Rúa de Urzáiz - Est. Intermodal - C.C."
+ },
+ "latitude": 42.233722977,
+ "longitude": -8.714502762,
+ "lines": [
+ "A",
+ "4A",
+ "4C",
+ "5A",
+ "6",
+ "9B",
+ "11",
+ "15B",
+ "15C",
+ "24",
+ "28",
+ "N4",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 8770,
+ "name": {
+ "original": "Rúa de Urzáiz 13"
+ },
+ "latitude": 42.235420929,
+ "longitude": -8.718721877,
+ "lines": [
+ "A",
+ "4A",
+ "4C",
+ "5A",
+ "6",
+ "7",
+ "9B",
+ "11",
+ "12B",
+ "14",
+ "15B",
+ "15C",
+ "16",
+ "17",
+ "18A",
+ "18B",
+ "18H",
+ "24",
+ "28",
+ "N1",
+ "N4",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 8820,
+ "name": {
+ "original": "Rúa de Urzáiz 28"
+ },
+ "latitude": 42.23516998,
+ "longitude": -8.718398782,
+ "lines": [
+ "C1",
+ "A",
+ "4A",
+ "4C",
+ "5A",
+ "7",
+ "9B",
+ "12B",
+ "14",
+ "15B",
+ "15C",
+ "16",
+ "17",
+ "18A",
+ "18B",
+ "18H",
+ "24",
+ "28",
+ "N1",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 8840,
+ "name": {
+ "original": "Rúa de Urzáiz 60 - Est. Intermodal - C.C."
+ },
+ "latitude": 42.233986283,
+ "longitude": -8.71541048,
+ "lines": [
+ "A",
+ "4A",
+ "4C",
+ "5A",
+ "9B",
+ "11",
+ "15B",
+ "15C",
+ "24",
+ "28",
+ "N1",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 8850,
+ "name": {
+ "original": "Rúa de Urzáiz 97"
+ },
+ "latitude": 42.232341315,
+ "longitude": -8.710892054,
+ "lines": [
+ "A",
+ "4A",
+ "4C",
+ "5A",
+ "6",
+ "9B",
+ "11",
+ "15B",
+ "15C",
+ "28",
+ "N4",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 8870,
+ "name": {
+ "original": "Rúa de Venezuela 4"
+ },
+ "latitude": 42.234250043,
+ "longitude": -8.724361531,
+ "lines": [
+ "4A",
+ "4C",
+ "5B",
+ "11",
+ "12A",
+ "12B",
+ "17",
+ "27",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 8880,
+ "name": {
+ "original": "Rúa de Venezuela 20"
+ },
+ "latitude": 42.233188126,
+ "longitude": -8.72155331,
+ "lines": [
+ "4A",
+ "4C",
+ "5B",
+ "11",
+ "12A",
+ "12B",
+ "17",
+ "27",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 8890,
+ "name": {
+ "original": "Rúa de Venezuela 21"
+ },
+ "latitude": 42.233283503,
+ "longitude": -8.721378959,
+ "lines": [
+ "4A",
+ "4C",
+ "5B",
+ "7",
+ "12B",
+ "16",
+ "17",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 8900,
+ "name": {
+ "original": "Rúa de Venezuela 45"
+ },
+ "latitude": 42.232243383,
+ "longitude": -8.718524158,
+ "lines": [
+ "4A",
+ "4C",
+ "5B",
+ "7",
+ "12B",
+ "16",
+ "17",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 8910,
+ "name": {
+ "original": "Rúa de Venezuela 42"
+ },
+ "latitude": 42.232224046,
+ "longitude": -8.718985824,
+ "lines": [
+ "4A",
+ "4C",
+ "5B",
+ "11",
+ "12A",
+ "12B",
+ "17",
+ "27",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 8916,
+ "name": {
+ "original": "Rúa de Venezuela 60"
+ },
+ "latitude": 42.231593651,
+ "longitude": -8.71714227,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 8930,
+ "name": {
+ "original": "Rúa de Vilagarcía de Arousa (cruce Rúa do Grove)"
+ },
+ "latitude": 42.22014115,
+ "longitude": -8.745082757,
+ "lines": [
+ "C3i",
+ "5B"
+ ]
+ },
+ {
+ "stopId": 8950,
+ "name": {
+ "original": "Rúa de Marín 5"
+ },
+ "latitude": 42.218712573,
+ "longitude": -8.75011435,
+ "lines": [
+ "C3i",
+ "5B"
+ ]
+ },
+ {
+ "stopId": 8970,
+ "name": {
+ "original": "Rúa do Seixo 45"
+ },
+ "latitude": 42.197425383,
+ "longitude": -8.713700535,
+ "lines": [
+ "A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 8980,
+ "name": {
+ "original": "Rúa do Seixo 38"
+ },
+ "latitude": 42.197532685,
+ "longitude": -8.713614705,
+ "lines": [
+ "A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 8990,
+ "name": {
+ "original": "Rúa do Seixo 75"
+ },
+ "latitude": 42.200673849,
+ "longitude": -8.714185609,
+ "lines": [
+ "A",
+ "18B",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 9000,
+ "name": {
+ "original": "Rúa do Seixo (Parque)"
+ },
+ "latitude": 42.200719549,
+ "longitude": -8.714115872,
+ "lines": [
+ "A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 9010,
+ "name": {
+ "original": "Rúa de Xeme 71"
+ },
+ "latitude": 42.203157887,
+ "longitude": -8.694293108,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 9020,
+ "name": {
+ "original": "Xestoso"
+ },
+ "latitude": 42.207584622,
+ "longitude": -8.670108196,
+ "lines": [
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 9040,
+ "name": {
+ "original": "Estrada das Plantas (cruce Camiño Monte Vello)"
+ },
+ "latitude": 42.20831564,
+ "longitude": -8.670282438,
+ "lines": [
+ "15B",
+ "15C",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 9050,
+ "name": {
+ "original": "Estrada da Igrexa 45"
+ },
+ "latitude": 42.154646971,
+ "longitude": -8.688349062,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 10061,
+ "name": {
+ "original": "Estrada de San Xoán 193"
+ },
+ "latitude": 42.185277472,
+ "longitude": -8.741558953,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14101,
+ "name": {
+ "original": "Estrada da Garrida 165"
+ },
+ "latitude": 42.168008539,
+ "longitude": -8.710415438,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14102,
+ "name": {
+ "original": "Estrada da Garrida 108"
+ },
+ "latitude": 42.168282882,
+ "longitude": -8.710066751,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14105,
+ "name": {
+ "original": "Ciencias Xurídicas (CUVI)"
+ },
+ "latitude": 42.167237978,
+ "longitude": -8.681135704,
+ "lines": [
+ "A",
+ "15C",
+ "U1",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 14106,
+ "name": {
+ "original": "Avda. do Aeroporto 92"
+ },
+ "latitude": 42.234161582,
+ "longitude": -8.695074564,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 14107,
+ "name": {
+ "original": "Camiño Padín (Rotonda Autoestrada)"
+ },
+ "latitude": 42.257847205,
+ "longitude": -8.677696507,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 14108,
+ "name": {
+ "original": "Avda. da Ponte (antes desvío Autovía)"
+ },
+ "latitude": 42.21401741,
+ "longitude": -8.67133083,
+ "lines": [
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 14111,
+ "name": {
+ "original": "Estrada de Bembrive (Centro Saúde)"
+ },
+ "latitude": 42.204262657,
+ "longitude": -8.684801801,
+ "lines": [
+ "6",
+ "14"
+ ]
+ },
+ {
+ "stopId": 14112,
+ "name": {
+ "original": "Estrada de Bembrive (Alameda)"
+ },
+ "latitude": 42.204047198,
+ "longitude": -8.684697288,
+ "lines": [
+ "6",
+ "14"
+ ]
+ },
+ {
+ "stopId": 14113,
+ "name": {
+ "original": "Estrada da Coutada 20"
+ },
+ "latitude": 42.193458577,
+ "longitude": -8.702065856,
+ "lines": [
+ "6",
+ "27"
+ ]
+ },
+ {
+ "stopId": 14117,
+ "name": {
+ "original": "Rúa do Couto de San Honorato 26"
+ },
+ "latitude": 42.228574702,
+ "longitude": -8.712864548,
+ "lines": [
+ "H2"
+ ]
+ },
+ {
+ "stopId": 14119,
+ "name": {
+ "original": "Rúa do Couto de San Honorato 80"
+ },
+ "latitude": 42.229320789,
+ "longitude": -8.710390551,
+ "lines": [
+ "H2"
+ ]
+ },
+ {
+ "stopId": 14121,
+ "name": {
+ "original": "Rúa da Reconquista 2"
+ },
+ "latitude": 42.238625474,
+ "longitude": -8.723242095,
+ "lines": [
+ "C1",
+ "C3d",
+ "A",
+ "5A",
+ "9B",
+ "10",
+ "15B",
+ "15C",
+ "24",
+ "28",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 14122,
+ "name": {
+ "original": "Avda. do Alcalde Gregorio Espino 9"
+ },
+ "latitude": 42.231584097,
+ "longitude": -8.706968521,
+ "lines": [
+ "4C",
+ "23",
+ "31",
+ "H2",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14123,
+ "name": {
+ "original": "Rúa do Porriño 9"
+ },
+ "latitude": 42.214127819,
+ "longitude": -8.752027594,
+ "lines": [
+ "C3d",
+ "C3i",
+ "4A",
+ "4C",
+ "5B",
+ "11",
+ "15A",
+ "15B",
+ "N4",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 14124,
+ "name": {
+ "original": "Rúa de Eduardo Cabello (fronte Igrexa)"
+ },
+ "latitude": 42.226569499,
+ "longitude": -8.752773946,
+ "lines": [
+ "C3d",
+ "C3i",
+ "6"
+ ]
+ },
+ {
+ "stopId": 14125,
+ "name": {
+ "original": "Rúa do Porriño (fronte 9)"
+ },
+ "latitude": 42.213869651,
+ "longitude": -8.751990789,
+ "lines": [
+ "C3d",
+ "C3i",
+ "4A",
+ "4C",
+ "15A",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14126,
+ "name": {
+ "original": "Rúa da Travesía de Vigo 194"
+ },
+ "latitude": 42.242494425,
+ "longitude": -8.699249038,
+ "lines": [
+ "C3i",
+ "5A",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 14127,
+ "name": {
+ "original": "Avda. de Buenos Aires 13"
+ },
+ "latitude": 42.249306896,
+ "longitude": -8.695179916,
+ "lines": [
+ "5B",
+ "10",
+ "N1",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 14128,
+ "name": {
+ "original": "Camiño do Caramuxo (fronte 9)"
+ },
+ "latitude": 42.20733292,
+ "longitude": -8.752159103,
+ "lines": [
+ "5A"
+ ]
+ },
+ {
+ "stopId": 14129,
+ "name": {
+ "original": "Camiño do Caramuxo 11"
+ },
+ "latitude": 42.20723039,
+ "longitude": -8.752592351,
+ "lines": [
+ "5A"
+ ]
+ },
+ {
+ "stopId": 14131,
+ "name": {
+ "original": "Rúa de Tomás Paredes 4"
+ },
+ "latitude": 42.221948768,
+ "longitude": -8.753171211,
+ "lines": [
+ "C3i",
+ "10"
+ ]
+ },
+ {
+ "stopId": 14132,
+ "name": {
+ "original": "Rúa de Sanjurjo Badía 252"
+ },
+ "latitude": 42.249307631,
+ "longitude": -8.696542008,
+ "lines": [
+ "C3d",
+ "5A",
+ "5B",
+ "10",
+ "17",
+ "31",
+ "U2",
+ "H2",
+ "H3",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 14133,
+ "name": {
+ "original": "Avda. de Galicia 37"
+ },
+ "latitude": 42.250977575,
+ "longitude": -8.694471881,
+ "lines": [
+ "C3i",
+ "17"
+ ]
+ },
+ {
+ "stopId": 14134,
+ "name": {
+ "original": "Avda. de Galicia 182"
+ },
+ "latitude": 42.253208793,
+ "longitude": -8.686995591,
+ "lines": [
+ "C3d"
+ ]
+ },
+ {
+ "stopId": 14135,
+ "name": {
+ "original": "Rúa de Santo Amaro (Praza de España)"
+ },
+ "latitude": 42.229174145,
+ "longitude": -8.720143055,
+ "lines": [
+ "C1"
+ ]
+ },
+ {
+ "stopId": 14136,
+ "name": {
+ "original": "Avda. de Galicia 18"
+ },
+ "latitude": 42.250484372,
+ "longitude": -8.694878804,
+ "lines": [
+ "C3d",
+ "17"
+ ]
+ },
+ {
+ "stopId": 14137,
+ "name": {
+ "original": "Estrada Matamá Pazo (Igrexa)"
+ },
+ "latitude": 42.200003406,
+ "longitude": -8.753169,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 14138,
+ "name": {
+ "original": "Estrada de Madrid (Campo de Fútbol)"
+ },
+ "latitude": 42.216459201,
+ "longitude": -8.678591709,
+ "lines": [
+ "12B",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 14139,
+ "name": {
+ "original": "Avda. de E. Martínez Garrido 27"
+ },
+ "latitude": 42.227492758,
+ "longitude": -8.700413366,
+ "lines": [
+ "6",
+ "25",
+ "31"
+ ]
+ },
+ {
+ "stopId": 14140,
+ "name": {
+ "original": "Avda. de E. Martínez Garrido 30"
+ },
+ "latitude": 42.228210877,
+ "longitude": -8.699999354,
+ "lines": [
+ "4C",
+ "6",
+ "23",
+ "25",
+ "31",
+ "N4",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14141,
+ "name": {
+ "original": "Rúa de Jenaro de la Fuente 43"
+ },
+ "latitude": 42.231379202,
+ "longitude": -8.699876213,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 14142,
+ "name": {
+ "original": "Avda. da Hispanidade 22"
+ },
+ "latitude": 42.231463434,
+ "longitude": -8.728844425,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 14143,
+ "name": {
+ "original": "Avda. da Hispanidade 38"
+ },
+ "latitude": 42.229753483,
+ "longitude": -8.729002675,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 14144,
+ "name": {
+ "original": "Avda. da Hispanidade 82"
+ },
+ "latitude": 42.226760436,
+ "longitude": -8.727385303,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 14150,
+ "name": {
+ "original": "Rúa do Padre Don Rúa 1"
+ },
+ "latitude": 42.232076561,
+ "longitude": -8.719055236,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 14152,
+ "name": {
+ "original": "Rúa do Monte Calvario 4"
+ },
+ "latitude": 42.204815402,
+ "longitude": -8.687168969,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 14153,
+ "name": {
+ "original": "Estrada de Bembrive 173"
+ },
+ "latitude": 42.205357233,
+ "longitude": -8.692495739,
+ "lines": [
+ "6",
+ "14"
+ ]
+ },
+ {
+ "stopId": 14154,
+ "name": {
+ "original": "Rúa das Chans (fronte 56)"
+ },
+ "latitude": 42.19360258,
+ "longitude": -8.677258993,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 14156,
+ "name": {
+ "original": "Rúa de Xeme (cruce Camiño da Carballeira)"
+ },
+ "latitude": 42.203378431,
+ "longitude": -8.696666863,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 14157,
+ "name": {
+ "original": "Rúa do Xeme (cruce Rúa de Eifonso)"
+ },
+ "latitude": 42.202979066,
+ "longitude": -8.694065121,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 14161,
+ "name": {
+ "original": "Rúa de López Mora 62"
+ },
+ "latitude": 42.224130699,
+ "longitude": -8.732568248,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 14162,
+ "name": {
+ "original": "Avda. da Florida 82"
+ },
+ "latitude": 42.211371871,
+ "longitude": -8.746523782,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 14163,
+ "name": {
+ "original": "Avda. da Florida (fronte 82)"
+ },
+ "latitude": 42.211442202,
+ "longitude": -8.746227469,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 14164,
+ "name": {
+ "original": "Rúa de Tomás A. Alonso 136"
+ },
+ "latitude": 42.225172437,
+ "longitude": -8.744777354,
+ "lines": [
+ "C3i",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 14165,
+ "name": {
+ "original": "Rúa de Tomás A. Alonso 193"
+ },
+ "latitude": 42.224905029,
+ "longitude": -8.745285775,
+ "lines": [
+ "C3d",
+ "13",
+ "15B",
+ "15C",
+ "U1",
+ "H"
+ ]
+ },
+ {
+ "stopId": 14166,
+ "name": {
+ "original": "Avda. das Camelias 114"
+ },
+ "latitude": 42.225142981,
+ "longitude": -8.729707944,
+ "lines": [
+ "4A",
+ "4C",
+ "7",
+ "12B",
+ "17",
+ "27",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14167,
+ "name": {
+ "original": "Beiramar - Pescadores"
+ },
+ "latitude": 42.225279021,
+ "longitude": -8.751908648,
+ "lines": [
+ "6",
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 14168,
+ "name": {
+ "original": "Avda. das Camelias 113"
+ },
+ "latitude": 42.224928285,
+ "longitude": -8.729631509,
+ "lines": [
+ "4A",
+ "4C",
+ "11",
+ "12B",
+ "17",
+ "27",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 14169,
+ "name": {
+ "original": "Avda. das Camelias 136"
+ },
+ "latitude": 42.22244224,
+ "longitude": -8.731271052,
+ "lines": [
+ "C1",
+ "4A",
+ "4C",
+ "7",
+ "12B",
+ "16",
+ "17",
+ "27",
+ "LZH",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14170,
+ "name": {
+ "original": "Avda. de Samil (Praia da Punta)"
+ },
+ "latitude": 42.218831744,
+ "longitude": -8.77571001,
+ "lines": [
+ "10",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 14171,
+ "name": {
+ "original": "Avda. de Samil (fronte Praia da Punta)"
+ },
+ "latitude": 42.218844713,
+ "longitude": -8.775459221,
+ "lines": [
+ "10",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 14173,
+ "name": {
+ "original": "Rúa do Gaiteiro de Ricardo Portela (fronte Pavillón)"
+ },
+ "latitude": 42.235900754,
+ "longitude": -8.731391435,
+ "lines": [
+ "C1",
+ "C3d",
+ "A",
+ "5A",
+ "9B",
+ "15C",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 14174,
+ "name": {
+ "original": "Rúa do Padre Seixas (Parque da Bouza)"
+ },
+ "latitude": 42.211844516,
+ "longitude": -8.749287921,
+ "lines": [
+ "11",
+ "16"
+ ]
+ },
+ {
+ "stopId": 14175,
+ "name": {
+ "original": "Rúa do Padre Seixas 32"
+ },
+ "latitude": 42.211792864,
+ "longitude": -8.749617832,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 14177,
+ "name": {
+ "original": "Rúa de Fernando Conde (cruce Avda. da Gran Vía)"
+ },
+ "latitude": 42.22985125,
+ "longitude": -8.71972059,
+ "lines": [
+ "12A",
+ "14",
+ "27"
+ ]
+ },
+ {
+ "stopId": 14178,
+ "name": {
+ "original": "Rúa do Marqués de Alcedo (Parque)"
+ },
+ "latitude": 42.233009005,
+ "longitude": -8.724497604,
+ "lines": [
+ "12A",
+ "27"
+ ]
+ },
+ {
+ "stopId": 14179,
+ "name": {
+ "original": "Rúa da Costa 4"
+ },
+ "latitude": 42.213260612,
+ "longitude": -8.722562576,
+ "lines": [
+ "A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 14180,
+ "name": {
+ "original": "Avda. de E. Martínez Garrido 108"
+ },
+ "latitude": 42.224749197,
+ "longitude": -8.707320585,
+ "lines": [
+ "4C",
+ "23",
+ "31",
+ "N4",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14181,
+ "name": {
+ "original": "Camiño da Corredoura (Igrexa)"
+ },
+ "latitude": 42.210954716,
+ "longitude": -8.727776522,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 14182,
+ "name": {
+ "original": "Rúa da Costa 39"
+ },
+ "latitude": 42.211618245,
+ "longitude": -8.72147159,
+ "lines": [
+ "A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 14183,
+ "name": {
+ "original": "Rúa do Xalón (Colexio)"
+ },
+ "latitude": 42.220622235,
+ "longitude": -8.654888024,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 14184,
+ "name": {
+ "original": "Rúa do Xalón 5"
+ },
+ "latitude": 42.217384986,
+ "longitude": -8.657082399,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 14185,
+ "name": {
+ "original": "Rúa da Becerreira 81"
+ },
+ "latitude": 42.219667098,
+ "longitude": -8.659470523,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 14186,
+ "name": {
+ "original": "Rúa da Becerreira (fronte 64)"
+ },
+ "latitude": 42.218044517,
+ "longitude": -8.662618478,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 14187,
+ "name": {
+ "original": "Rúa da Becerreira 1"
+ },
+ "latitude": 42.221588029,
+ "longitude": -8.662035851,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 14188,
+ "name": {
+ "original": "Rúa da Becerreira 41"
+ },
+ "latitude": 42.217595578,
+ "longitude": -8.661414166,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 14189,
+ "name": {
+ "original": "Rúa Molais 84"
+ },
+ "latitude": 42.226081487,
+ "longitude": -8.654133203,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 14190,
+ "name": {
+ "original": "Rúa de Severino Cobas 196"
+ },
+ "latitude": 42.225080876,
+ "longitude": -8.683314171,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14191,
+ "name": {
+ "original": "Camiño da Bouciña 76"
+ },
+ "latitude": 42.223737557,
+ "longitude": -8.682141153,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14192,
+ "name": {
+ "original": "Rúa das Figueiras 282"
+ },
+ "latitude": 42.231625599,
+ "longitude": -8.652046516,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14193,
+ "name": {
+ "original": "Avda. de Santa Mariña 443"
+ },
+ "latitude": 42.230916628,
+ "longitude": -8.641628816,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14194,
+ "name": {
+ "original": "Avda. de Santa Mariña 425"
+ },
+ "latitude": 42.228639377,
+ "longitude": -8.640978361,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14195,
+ "name": {
+ "original": "Avda. de Santa Mariña 249"
+ },
+ "latitude": 42.226263256,
+ "longitude": -8.644091084,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14196,
+ "name": {
+ "original": "Avda. de Santa Mariña 229"
+ },
+ "latitude": 42.225296,
+ "longitude": -8.649527921,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14197,
+ "name": {
+ "original": "Rúa Molais (cruce Rúa das Carballas)"
+ },
+ "latitude": 42.225912491,
+ "longitude": -8.653698801,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14198,
+ "name": {
+ "original": "Rúa do Riomao 21"
+ },
+ "latitude": 42.227231301,
+ "longitude": -8.659997969,
+ "lines": [
+ "15A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 14199,
+ "name": {
+ "original": "Camiño da Bouciña 79"
+ },
+ "latitude": 42.223755464,
+ "longitude": -8.682041911,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14200,
+ "name": {
+ "original": "Rúa de Severino Cobas 119"
+ },
+ "latitude": 42.225112655,
+ "longitude": -8.683402684,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14201,
+ "name": {
+ "original": "Avda. de Santa Mariña (cruce Avda. do Tranvía)"
+ },
+ "latitude": 42.226291056,
+ "longitude": -8.641647591,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14202,
+ "name": {
+ "original": "Rúa de Jenaro de la Fuente 58"
+ },
+ "latitude": 42.231273786,
+ "longitude": -8.700145645,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 14203,
+ "name": {
+ "original": "Avda. do Tranvía S/N (despois Camiño Lugar)"
+ },
+ "latitude": 42.226524401,
+ "longitude": -8.661251786,
+ "lines": [
+ "11",
+ "15A"
+ ]
+ },
+ {
+ "stopId": 14204,
+ "name": {
+ "original": "Rúa de Manuel Álvarez 151"
+ },
+ "latitude": 42.22312688,
+ "longitude": -8.681864633,
+ "lines": [
+ "25",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 14205,
+ "name": {
+ "original": "Estrada do Freixo (Cemiterio)"
+ },
+ "latitude": 42.178408629,
+ "longitude": -8.733198549,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14206,
+ "name": {
+ "original": "Avda. da Gran Vía (Instituto)"
+ },
+ "latitude": 42.220514043,
+ "longitude": -8.731700217,
+ "lines": [
+ "C3i",
+ "7",
+ "11",
+ "13",
+ "15A",
+ "16",
+ "23",
+ "29",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 14207,
+ "name": {
+ "original": "Camiño do Pinal 19"
+ },
+ "latitude": 42.161212162,
+ "longitude": -8.716377433,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14208,
+ "name": {
+ "original": "Estrada de Valadares 452"
+ },
+ "latitude": 42.162237207,
+ "longitude": -8.71885531,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14209,
+ "name": {
+ "original": "Estrada da Garrida 263"
+ },
+ "latitude": 42.173333822,
+ "longitude": -8.705439803,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14210,
+ "name": {
+ "original": "Estrada da Garrida (frente 243)"
+ },
+ "latitude": 42.173077394,
+ "longitude": -8.705659744,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14211,
+ "name": {
+ "original": "Estrada do Monte Alba 32"
+ },
+ "latitude": 42.165479162,
+ "longitude": -8.721775005,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14212,
+ "name": {
+ "original": "Estrada do Monte Alba 54"
+ },
+ "latitude": 42.164694101,
+ "longitude": -8.724472962,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14213,
+ "name": {
+ "original": "Estrada do Freixo (Campo Fútbol)"
+ },
+ "latitude": 42.169153241,
+ "longitude": -8.729001464,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14214,
+ "name": {
+ "original": "Estrada do Freixo 191"
+ },
+ "latitude": 42.175760907,
+ "longitude": -8.734516924,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14215,
+ "name": {
+ "original": "Estrada do Freixo 90"
+ },
+ "latitude": 42.175592169,
+ "longitude": -8.734477788,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14216,
+ "name": {
+ "original": "Estrada do Freixo (fronte Campo Fútbol)"
+ },
+ "latitude": 42.169143301,
+ "longitude": -8.729076566,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14217,
+ "name": {
+ "original": "Estrada do Monte Alba (frente 54)"
+ },
+ "latitude": 42.164570839,
+ "longitude": -8.724561475,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14218,
+ "name": {
+ "original": "Estrada do Monte Alba 21"
+ },
+ "latitude": 42.165288305,
+ "longitude": -8.721367309,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14219,
+ "name": {
+ "original": "Camiño do Pinal 59"
+ },
+ "latitude": 42.159500512,
+ "longitude": -8.718247279,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14220,
+ "name": {
+ "original": "Camiño da Bouciña 14"
+ },
+ "latitude": 42.225657321,
+ "longitude": -8.681467666,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14221,
+ "name": {
+ "original": "Camiño da Bouciña 3"
+ },
+ "latitude": 42.225809218,
+ "longitude": -8.681652991,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14222,
+ "name": {
+ "original": "Camiño do Pinal 5"
+ },
+ "latitude": 42.162959525,
+ "longitude": -8.716541365,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14223,
+ "name": {
+ "original": "Avda. Beiramar \"Porto Pesqueiro Berbés\""
+ },
+ "latitude": 42.236650106,
+ "longitude": -8.73161427,
+ "lines": [
+ "C3i",
+ "A",
+ "5A",
+ "5B",
+ "10",
+ "11",
+ "15C",
+ "N1",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 14224,
+ "name": {
+ "original": "Rúa do Conde de Torrecedeira 16"
+ },
+ "latitude": 42.234285927,
+ "longitude": -8.731266507,
+ "lines": [
+ "C3i",
+ "A",
+ "5A",
+ "5B",
+ "10",
+ "11",
+ "15C",
+ "N1",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 14225,
+ "name": {
+ "original": "Rúa do Conde de Torrecedeira 50"
+ },
+ "latitude": 42.231716574,
+ "longitude": -8.732308737,
+ "lines": [
+ "C3i",
+ "A",
+ "5A",
+ "5B",
+ "10",
+ "11",
+ "15C",
+ "N1",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 14226,
+ "name": {
+ "original": "Rúa do Conde de Torrecedeira 70"
+ },
+ "latitude": 42.229574859,
+ "longitude": -8.733073973,
+ "lines": [
+ "C3i",
+ "A",
+ "5A",
+ "5B",
+ "10",
+ "11",
+ "15C",
+ "N1",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 14227,
+ "name": {
+ "original": "Rúa do Conde de Torrecedeira 86"
+ },
+ "latitude": 42.22696657,
+ "longitude": -8.734559706,
+ "lines": [
+ "C3i",
+ "A",
+ "5A",
+ "5B",
+ "10",
+ "11",
+ "15C",
+ "N1",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 14228,
+ "name": {
+ "original": "Avda. de Peinador 100"
+ },
+ "latitude": 42.221696342,
+ "longitude": -8.632840997,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 14231,
+ "name": {
+ "original": "Rúa da Rabadeira 39"
+ },
+ "latitude": 42.235542066,
+ "longitude": -8.652196565,
+ "lines": [
+ "9B"
+ ]
+ },
+ {
+ "stopId": 14232,
+ "name": {
+ "original": "Rúa da Rabadeira (fronte 33)"
+ },
+ "latitude": 42.235317662,
+ "longitude": -8.652094641,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 14233,
+ "name": {
+ "original": "Rúa San Cristobo 90"
+ },
+ "latitude": 42.241037275,
+ "longitude": -8.668947597,
+ "lines": [
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 14236,
+ "name": {
+ "original": "Rúa de Manuel Cominges (fronte 112)"
+ },
+ "latitude": 42.196308523,
+ "longitude": -8.723526935,
+ "lines": [
+ "12B"
+ ]
+ },
+ {
+ "stopId": 14237,
+ "name": {
+ "original": "Rúa de Manuel Cominges 134"
+ },
+ "latitude": 42.196119748,
+ "longitude": -8.723457198,
+ "lines": [
+ "12B"
+ ]
+ },
+ {
+ "stopId": 14238,
+ "name": {
+ "original": "Rúa da Saa do Monte 5"
+ },
+ "latitude": 42.20458802,
+ "longitude": -8.714617309,
+ "lines": [
+ "18B"
+ ]
+ },
+ {
+ "stopId": 14240,
+ "name": {
+ "original": "Rúa das Chabarras 21"
+ },
+ "latitude": 42.197985091,
+ "longitude": -8.714523201,
+ "lines": [
+ "18B"
+ ]
+ },
+ {
+ "stopId": 14241,
+ "name": {
+ "original": "Rúa das Chabarras 60"
+ },
+ "latitude": 42.197842023,
+ "longitude": -8.71471632,
+ "lines": [
+ "18B"
+ ]
+ },
+ {
+ "stopId": 14242,
+ "name": {
+ "original": "Rúa das Chabarras (cruce Camiño dos Pasais)"
+ },
+ "latitude": 42.196378259,
+ "longitude": -8.716979043,
+ "lines": [
+ "18B"
+ ]
+ },
+ {
+ "stopId": 14243,
+ "name": {
+ "original": "Rúa das Chabarras 24"
+ },
+ "latitude": 42.196539214,
+ "longitude": -8.716874437,
+ "lines": [
+ "18B"
+ ]
+ },
+ {
+ "stopId": 14244,
+ "name": {
+ "original": "Rúa de Macal 60"
+ },
+ "latitude": 42.198216234,
+ "longitude": -8.721498041,
+ "lines": [
+ "18B",
+ "18H",
+ "27"
+ ]
+ },
+ {
+ "stopId": 14245,
+ "name": {
+ "original": "Avda. de García Barbón 43"
+ },
+ "latitude": 42.23691728,
+ "longitude": -8.716743143,
+ "lines": [
+ "C3i",
+ "5B",
+ "10",
+ "16",
+ "17",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 14247,
+ "name": {
+ "original": "Camiño dos Muíños 69"
+ },
+ "latitude": 42.200511179,
+ "longitude": -8.769110573,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 14248,
+ "name": {
+ "original": "Camiño dos Muíños 74"
+ },
+ "latitude": 42.200580723,
+ "longitude": -8.76911862,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 14249,
+ "name": {
+ "original": "Avda. de Cesáreo Vázquez 5"
+ },
+ "latitude": 42.177662554,
+ "longitude": -8.800157923,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 14250,
+ "name": {
+ "original": "Avda. do Aeroporto 463"
+ },
+ "latitude": 42.234914814,
+ "longitude": -8.658983411,
+ "lines": [
+ "A",
+ "9B"
+ ]
+ },
+ {
+ "stopId": 14251,
+ "name": {
+ "original": "Avda. do Aeroporto (fronte 463)"
+ },
+ "latitude": 42.234817602,
+ "longitude": -8.65882027,
+ "lines": [
+ "A",
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 14252,
+ "name": {
+ "original": "Estrada Clara Campoamor (Instituto)"
+ },
+ "latitude": 42.165934208,
+ "longitude": -8.707243001,
+ "lines": [
+ "U1"
+ ]
+ },
+ {
+ "stopId": 14253,
+ "name": {
+ "original": "Estrada Clara Campoamor (cruce Rúa do Padrón do Couto)"
+ },
+ "latitude": 42.164455564,
+ "longitude": -8.707223843,
+ "lines": [
+ "U1"
+ ]
+ },
+ {
+ "stopId": 14255,
+ "name": {
+ "original": "Rúa do Pintor Colmeiro (Parque do Pintor Colmeiro)"
+ },
+ "latitude": 42.225111918,
+ "longitude": -8.726733526,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 14256,
+ "name": {
+ "original": "Rúa de Zamora 89"
+ },
+ "latitude": 42.222198901,
+ "longitude": -8.728317834,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 14257,
+ "name": {
+ "original": "Rúa de Zamora 71"
+ },
+ "latitude": 42.223448271,
+ "longitude": -8.725547112,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 14258,
+ "name": {
+ "original": "Rúa de Zamora 51"
+ },
+ "latitude": 42.224870416,
+ "longitude": -8.723632015,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 14259,
+ "name": {
+ "original": "Rúa de Zamora 31"
+ },
+ "latitude": 42.227088982,
+ "longitude": -8.721545256,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 14260,
+ "name": {
+ "original": "Avda. da Gran Vía (fronte Avda. de Madrid)"
+ },
+ "latitude": 42.228741057,
+ "longitude": -8.71961914,
+ "lines": [
+ "7",
+ "14",
+ "15A",
+ "16",
+ "18A",
+ "18B",
+ "18H",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 14261,
+ "name": {
+ "original": "Rúa de Zamora 1"
+ },
+ "latitude": 42.228644118,
+ "longitude": -8.720692314,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 14264,
+ "name": {
+ "original": "Rúa de Urzáiz - Príncipe"
+ },
+ "latitude": 42.235873545,
+ "longitude": -8.720083317,
+ "lines": [
+ "C1",
+ "A",
+ "4A",
+ "4C",
+ "5A",
+ "7",
+ "9B",
+ "12B",
+ "14",
+ "15B",
+ "15C",
+ "16",
+ "17",
+ "18A",
+ "18B",
+ "18H",
+ "24",
+ "28",
+ "N1",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 14267,
+ "name": {
+ "original": "Avda. da Atlántida 64"
+ },
+ "latitude": 42.221892792,
+ "longitude": -8.758191526,
+ "lines": [
+ "10",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 14268,
+ "name": {
+ "original": "Avda. da Atlántida 49"
+ },
+ "latitude": 42.221731945,
+ "longitude": -8.758417175,
+ "lines": [
+ "10",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 14270,
+ "name": {
+ "original": "Estrada da Balsa 67"
+ },
+ "latitude": 42.196619218,
+ "longitude": -8.743240048,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14271,
+ "name": {
+ "original": "Estrada da Balsa 103"
+ },
+ "latitude": 42.196050474,
+ "longitude": -8.745105715,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14273,
+ "name": {
+ "original": "Rúa do Xalón 41"
+ },
+ "latitude": 42.219274062,
+ "longitude": -8.656419893,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 14277,
+ "name": {
+ "original": "Avda. da Mariña Española 8"
+ },
+ "latitude": 42.251776399,
+ "longitude": -8.69414009,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14278,
+ "name": {
+ "original": "Avda. da Mariña Española 44"
+ },
+ "latitude": 42.25430173,
+ "longitude": -8.692915616,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14279,
+ "name": {
+ "original": "Riós (Rotonda)"
+ },
+ "latitude": 42.257069093,
+ "longitude": -8.690786611,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14280,
+ "name": {
+ "original": "Avda. da Mariña Española (ETEA)"
+ },
+ "latitude": 42.254604716,
+ "longitude": -8.692539681,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14281,
+ "name": {
+ "original": "Avda. da Mariña Española (Praia de Ríos)"
+ },
+ "latitude": 42.251596707,
+ "longitude": -8.69420171,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14287,
+ "name": {
+ "original": "Rúa Santa Tegra 67"
+ },
+ "latitude": 42.25020334,
+ "longitude": -8.701924083,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14288,
+ "name": {
+ "original": "Avda. de Guixar (fronte 28)"
+ },
+ "latitude": 42.249218849,
+ "longitude": -8.704807605,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14289,
+ "name": {
+ "original": "Rúa de Xulián Estévez (fronte 58)"
+ },
+ "latitude": 42.246484972,
+ "longitude": -8.705864005,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14290,
+ "name": {
+ "original": "Rúa de Xulián Estévez (fronte 18)"
+ },
+ "latitude": 42.244107542,
+ "longitude": -8.706343638,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14291,
+ "name": {
+ "original": "Avda. da Ponte (Vigo Memorial)"
+ },
+ "latitude": 42.209935219,
+ "longitude": -8.671464542,
+ "lines": [
+ "12B",
+ "15B",
+ "15C",
+ "U2"
+ ]
+ },
+ {
+ "stopId": 14294,
+ "name": {
+ "original": "Avda. de Ricardo Mella 406"
+ },
+ "latitude": 42.190684424876565,
+ "longitude": -8.799308812770041,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 14295,
+ "name": {
+ "original": "Rúa de Pi i Margall 121"
+ },
+ "latitude": 42.230436358,
+ "longitude": -8.731437473,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 14296,
+ "name": {
+ "original": "Praza dos Leóns (Vigozoo)"
+ },
+ "latitude": 42.248375604,
+ "longitude": -8.675578666,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 14299,
+ "name": {
+ "original": "Avda. de Samil (frente Verbum)"
+ },
+ "latitude": 42.213644883,
+ "longitude": -8.774567214,
+ "lines": [
+ "C3i",
+ "15A",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 14300,
+ "name": {
+ "original": "Avda. da Florida 30"
+ },
+ "latitude": 42.217907548,
+ "longitude": -8.73707436,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 14301,
+ "name": {
+ "original": "Avda. da Florida 47"
+ },
+ "latitude": 42.218257459,
+ "longitude": -8.736328798,
+ "lines": [
+ "5A",
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 14302,
+ "name": {
+ "original": "Estrada Vella de Madrid 7"
+ },
+ "latitude": 42.214542094,
+ "longitude": -8.696431619,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 14304,
+ "name": {
+ "original": "Estrada Vella de Madrid 145"
+ },
+ "latitude": 42.221313975,
+ "longitude": -8.681944471,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "31"
+ ]
+ },
+ {
+ "stopId": 14307,
+ "name": {
+ "original": "Rúa do Pintor Colmeiro 11"
+ },
+ "latitude": 42.224464416,
+ "longitude": -8.727967343,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 14308,
+ "name": {
+ "original": "Camiño do Pinal 6"
+ },
+ "latitude": 42.16328558,
+ "longitude": -8.716707662,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14309,
+ "name": {
+ "original": "Camiño do Pinal 28"
+ },
+ "latitude": 42.161067029,
+ "longitude": -8.716468628,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14310,
+ "name": {
+ "original": "Camiño do Pinal (fronte 57)"
+ },
+ "latitude": 42.159564137,
+ "longitude": -8.718295559,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14311,
+ "name": {
+ "original": "Estrada de Valadares 505"
+ },
+ "latitude": 42.162720337,
+ "longitude": -8.718900908,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14314,
+ "name": {
+ "original": "Rúa das Mantelas (cruce Avda. da Gran Vía)"
+ },
+ "latitude": 42.227212568,
+ "longitude": -8.720183032,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14315,
+ "name": {
+ "original": "Rúa das Mantelas 92"
+ },
+ "latitude": 42.22393338,
+ "longitude": -8.716924148,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14317,
+ "name": {
+ "original": "Rúa da Salgueira Entrada (Igrexa)"
+ },
+ "latitude": 42.222723933,
+ "longitude": -8.719150283,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14318,
+ "name": {
+ "original": "Rúa da Salguera Entrada (fronte 5)"
+ },
+ "latitude": 42.222591256,
+ "longitude": -8.717753486,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14319,
+ "name": {
+ "original": "Rúa das Coutadas (Fonte)"
+ },
+ "latitude": 42.221002214,
+ "longitude": -8.72027208,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14320,
+ "name": {
+ "original": "Rúa do Miradoiro (Rotonda Centro Comercial)"
+ },
+ "latitude": 42.220799025,
+ "longitude": -8.723345356,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14321,
+ "name": {
+ "original": "Camiño do Freixeiro 74"
+ },
+ "latitude": 42.218131641,
+ "longitude": -8.723120057,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14322,
+ "name": {
+ "original": "Rúa da Fonte Santa 4"
+ },
+ "latitude": 42.217813814,
+ "longitude": -8.721352482,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14323,
+ "name": {
+ "original": "Rúa Finca dos Aires (cruce Rúa da Fonte Santa)"
+ },
+ "latitude": 42.217059742,
+ "longitude": -8.720340235,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14324,
+ "name": {
+ "original": "Rúa Finca dos Aires (Urbanización)"
+ },
+ "latitude": 42.217256401,
+ "longitude": -8.720101519,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14325,
+ "name": {
+ "original": "Rúa da Fonte Santa (fronte 4)"
+ },
+ "latitude": 42.217848286,
+ "longitude": -8.7214811,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14326,
+ "name": {
+ "original": "Baixada á Ponte Nova 61"
+ },
+ "latitude": 42.218038279,
+ "longitude": -8.722489738,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14328,
+ "name": {
+ "original": "Citroën - PSA"
+ },
+ "latitude": 42.208988415,
+ "longitude": -8.746151897,
+ "lines": [
+ "LZH",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14329,
+ "name": {
+ "original": "Citroën (Puerta Principal)"
+ },
+ "latitude": 42.210124372,
+ "longitude": -8.741139991,
+ "lines": [
+ "LZH",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14330,
+ "name": {
+ "original": "Subida ás Chans (fronte cruce Rúa Senra)"
+ },
+ "latitude": 42.198072667,
+ "longitude": -8.682624653,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 14331,
+ "name": {
+ "original": "Rúa de Álvaro Cunqueiro 30"
+ },
+ "latitude": 42.223769828,
+ "longitude": -8.728938728,
+ "lines": [
+ "4A",
+ "4C",
+ "5A",
+ "5B",
+ "11",
+ "12A",
+ "12B",
+ "17",
+ "27",
+ "N1",
+ "LZH"
+ ]
+ },
+ {
+ "stopId": 14333,
+ "name": {
+ "original": "Rúa de Cánovas del Castillo 18"
+ },
+ "latitude": 42.240189011,
+ "longitude": -8.726765331,
+ "lines": [
+ "C1",
+ "C3d",
+ "A",
+ "5A",
+ "9B",
+ "10",
+ "15B",
+ "15C",
+ "28",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 14335,
+ "name": {
+ "original": "Camiño do Arieiro (Residencia de Maiores)"
+ },
+ "latitude": 42.212692269,
+ "longitude": -8.675661599,
+ "lines": [
+ "31"
+ ]
+ },
+ {
+ "stopId": 14336,
+ "name": {
+ "original": "Rúa das Teixugueiras 8"
+ },
+ "latitude": 42.21447626,
+ "longitude": -8.75600551,
+ "lines": [
+ "13",
+ "15A"
+ ]
+ },
+ {
+ "stopId": 14337,
+ "name": {
+ "original": "Rúa do Limpiño (Rotonda Rúa Teixugueiras)"
+ },
+ "latitude": 42.213080218,
+ "longitude": -8.754660224,
+ "lines": [
+ "5A",
+ "5B",
+ "13",
+ "15A",
+ "15B",
+ "N4",
+ "U1",
+ "H",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14345,
+ "name": {
+ "original": "Rúa do Areiro 49"
+ },
+ "latitude": 42.237269816,
+ "longitude": -8.685138009,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 14346,
+ "name": {
+ "original": "Rúa do Areiro 52"
+ },
+ "latitude": 42.237287688,
+ "longitude": -8.685019992,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 14347,
+ "name": {
+ "original": "Rúa do Areiro (cruce Salcides)"
+ },
+ "latitude": 42.23913765,
+ "longitude": -8.683873934,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 14348,
+ "name": {
+ "original": "Rúa do Areiro 76"
+ },
+ "latitude": 42.238950988,
+ "longitude": -8.683793467,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 14349,
+ "name": {
+ "original": "Rúa do Areiro (Campo de Fútbol)"
+ },
+ "latitude": 42.244717312,
+ "longitude": -8.678473607,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 14350,
+ "name": {
+ "original": "Rúa do Areiro (fronte Campo de Fútbol)"
+ },
+ "latitude": 42.244849353,
+ "longitude": -8.678366319,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 14353,
+ "name": {
+ "original": "Praza dos Leóns (fronte Vigozoo)"
+ },
+ "latitude": 42.248151648,
+ "longitude": -8.675976097,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 14354,
+ "name": {
+ "original": "Avda. de Ramón Nieto (fronte Igrexa)"
+ },
+ "latitude": 42.225911433,
+ "longitude": -8.675526243,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 14355,
+ "name": {
+ "original": "Avda. de Ramón Nieto 409"
+ },
+ "latitude": 42.226638363,
+ "longitude": -8.676135104,
+ "lines": [
+ "11",
+ "15A",
+ "15B",
+ "15C",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 14356,
+ "name": {
+ "original": "Avda. de Ricardo Mella 314"
+ },
+ "latitude": 42.192009114,
+ "longitude": -8.783993123,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 14357,
+ "name": {
+ "original": "Avda. de Ricardo Mella (cruce Camiño do Río)"
+ },
+ "latitude": 42.191796473,
+ "longitude": -8.784014088,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 14358,
+ "name": {
+ "original": "Rúa das Teixugueiras 28"
+ },
+ "latitude": 42.209054557,
+ "longitude": -8.75715865,
+ "lines": [
+ "5A",
+ "5B",
+ "13",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 14359,
+ "name": {
+ "original": "Rúa das Teixugueiras 16-Portal 2"
+ },
+ "latitude": 42.212235738,
+ "longitude": -8.755011746,
+ "lines": [
+ "5A",
+ "5B",
+ "13",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 14360,
+ "name": {
+ "original": "Rúa das Teixugueiras 19-Portal 5"
+ },
+ "latitude": 42.208965857,
+ "longitude": -8.757020567,
+ "lines": [
+ "5A",
+ "5B",
+ "13",
+ "N4",
+ "U1",
+ "H",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14361,
+ "name": {
+ "original": "Rúa das Teixugueiras 17"
+ },
+ "latitude": 42.209770472,
+ "longitude": -8.755295907,
+ "lines": [
+ "5A",
+ "5B",
+ "13",
+ "15A",
+ "N4",
+ "U1",
+ "H",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14362,
+ "name": {
+ "original": "Avda. de Samil 101"
+ },
+ "latitude": 42.202937872,
+ "longitude": -8.776830486,
+ "lines": [
+ "C3d",
+ "4C",
+ "10"
+ ]
+ },
+ {
+ "stopId": 14364,
+ "name": {
+ "original": "Estrada das Plantas (fronte Cidade Deportiva)"
+ },
+ "latitude": 42.175757186,
+ "longitude": -8.671074371,
+ "lines": [
+ "15C"
+ ]
+ },
+ {
+ "stopId": 14365,
+ "name": {
+ "original": "Estrada das Plantas (Viveiros)"
+ },
+ "latitude": 42.181650197,
+ "longitude": -8.667515723,
+ "lines": [
+ "15C"
+ ]
+ },
+ {
+ "stopId": 14372,
+ "name": {
+ "original": "Barrio da Salgueira 22"
+ },
+ "latitude": 42.221887526,
+ "longitude": -8.720011371,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 14376,
+ "name": {
+ "original": "Rúa da Pateira 20"
+ },
+ "latitude": 42.226612651,
+ "longitude": -8.699658408,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14377,
+ "name": {
+ "original": "Rúa da Pateira 5"
+ },
+ "latitude": 42.226582661,
+ "longitude": -8.700385762,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14378,
+ "name": {
+ "original": "Rúa Molais 83"
+ },
+ "latitude": 42.22396201,
+ "longitude": -8.653340726,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14381,
+ "name": {
+ "original": "Rúa do Abade Juan de Bastos 6"
+ },
+ "latitude": 42.195647685,
+ "longitude": -8.728974153,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14383,
+ "name": {
+ "original": "Estrada Clara Campoamor (cruce Estrada do Portal)"
+ },
+ "latitude": 42.174718265,
+ "longitude": -8.713684656,
+ "lines": [
+ "U1"
+ ]
+ },
+ {
+ "stopId": 14384,
+ "name": {
+ "original": "Estrada Clara Campoamor (Parque Tecnolóxico)"
+ },
+ "latitude": 42.175073486,
+ "longitude": -8.713494654,
+ "lines": [
+ "U1"
+ ]
+ },
+ {
+ "stopId": 14385,
+ "name": {
+ "original": "Rúa da Vista do Mar 45"
+ },
+ "latitude": 42.240712912,
+ "longitude": -8.6919418,
+ "lines": [
+ "4A",
+ "24"
+ ]
+ },
+ {
+ "stopId": 14386,
+ "name": {
+ "original": "Rúa da Vista do Mar (Embalse)"
+ },
+ "latitude": 42.238020208,
+ "longitude": -8.691543884,
+ "lines": [
+ "4A",
+ "24"
+ ]
+ },
+ {
+ "stopId": 14387,
+ "name": {
+ "original": "Rúa da Vista do Mar 1"
+ },
+ "latitude": 42.237167043,
+ "longitude": -8.693243792,
+ "lines": [
+ "4A",
+ "24"
+ ]
+ },
+ {
+ "stopId": 14388,
+ "name": {
+ "original": "Rúa das Teixugueiras 11"
+ },
+ "latitude": 42.211610124,
+ "longitude": -8.754550253,
+ "lines": [
+ "5A",
+ "5B",
+ "13",
+ "15A",
+ "N4",
+ "U1",
+ "H",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14389,
+ "name": {
+ "original": "Rúa das Teixugueiras 22"
+ },
+ "latitude": 42.210212603,
+ "longitude": -8.755079989,
+ "lines": [
+ "5A",
+ "5B",
+ "13",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 14390,
+ "name": {
+ "original": "Rúa do Salgueiro (cruce Camiño Sanatorio)"
+ },
+ "latitude": 42.241570883,
+ "longitude": -8.655380719,
+ "lines": [
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 14391,
+ "name": {
+ "original": "Rúa do Salgueiro (fronte 38)"
+ },
+ "latitude": 42.241652296,
+ "longitude": -8.655302935,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 14392,
+ "name": {
+ "original": "Rúa da Pedra Seixa (Colexio)"
+ },
+ "latitude": 42.209458591,
+ "longitude": -8.760561083,
+ "lines": [
+ "5A"
+ ]
+ },
+ {
+ "stopId": 14393,
+ "name": {
+ "original": "Rúa da Pedra Seixa (fronte Colexio)"
+ },
+ "latitude": 42.209568936,
+ "longitude": -8.760777001,
+ "lines": [
+ "5A"
+ ]
+ },
+ {
+ "stopId": 14395,
+ "name": {
+ "original": "Estrada de Madrid 217"
+ },
+ "latitude": 42.215601037,
+ "longitude": -8.675477665,
+ "lines": [
+ "12B",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 14396,
+ "name": {
+ "original": "Avda. de García Barbón 106"
+ },
+ "latitude": 42.239965365,
+ "longitude": -8.708024282,
+ "lines": [
+ "C3d",
+ "5B",
+ "10",
+ "17",
+ "31",
+ "H2",
+ "H3",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 14397,
+ "name": {
+ "original": "Avda. de García Barbón (fronte 104)"
+ },
+ "latitude": 42.23973713,
+ "longitude": -8.708397682,
+ "lines": [
+ "C3i",
+ "5B",
+ "10",
+ "17",
+ "N1",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 14398,
+ "name": {
+ "original": "Avda. da Gran Vía 116"
+ },
+ "latitude": 42.22406594,
+ "longitude": -8.723691036,
+ "lines": [
+ "C3d",
+ "13",
+ "15A",
+ "23",
+ "29",
+ "H2",
+ "PSA 1"
+ ]
+ },
+ {
+ "stopId": 14401,
+ "name": {
+ "original": "Rúa de San Paio (cruce Camiño Barroca)"
+ },
+ "latitude": 42.21072167,
+ "longitude": -8.76212542,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 14402,
+ "name": {
+ "original": "Rúa de San Paio 111"
+ },
+ "latitude": 42.210791203,
+ "longitude": -8.761940347,
+ "lines": [
+ "4A",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 14403,
+ "name": {
+ "original": "Rúa de Pedro Alvarado (cruce Camiño das Maceiras)"
+ },
+ "latitude": 42.25049654,
+ "longitude": -8.698390035,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14404,
+ "name": {
+ "original": "Rúa do Doutor Corbal 58"
+ },
+ "latitude": 42.251785642,
+ "longitude": -8.696871994,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14406,
+ "name": {
+ "original": "Rúa de Enrique Lorenzo 32"
+ },
+ "latitude": 42.249462772,
+ "longitude": -8.699772952,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14408,
+ "name": {
+ "original": "Rúa de Pedro Alvarado 5"
+ },
+ "latitude": 42.248885006,
+ "longitude": -8.698128758,
+ "lines": [
+ "17"
+ ]
+ },
+ {
+ "stopId": 14409,
+ "name": {
+ "original": "Estrada das Plantas (fronte cruce Avda. do Rebullón)"
+ },
+ "latitude": 42.204188441,
+ "longitude": -8.670257126,
+ "lines": [
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 14410,
+ "name": {
+ "original": "Estrada das Plantas (cruce Avda. do Rebullón)"
+ },
+ "latitude": 42.204639457,
+ "longitude": -8.670329545,
+ "lines": [
+ "15C"
+ ]
+ },
+ {
+ "stopId": 14411,
+ "name": {
+ "original": "Rúa de Xeme 1"
+ },
+ "latitude": 42.205174543,
+ "longitude": -8.698209134,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 14412,
+ "name": {
+ "original": "Rúa de Xeme 6"
+ },
+ "latitude": 42.20497586,
+ "longitude": -8.697957006,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 14413,
+ "name": {
+ "original": "Estrada da Garrida (cruce Camiño Fabas)"
+ },
+ "latitude": 42.173955568,
+ "longitude": -8.703050076,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 14414,
+ "name": {
+ "original": "Avda. de Santa Mariña 40"
+ },
+ "latitude": 42.221587211,
+ "longitude": -8.665078444,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 14415,
+ "name": {
+ "original": "Avda. de Santa Mariña (antes 49)"
+ },
+ "latitude": 42.221557416,
+ "longitude": -8.665381534,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 14416,
+ "name": {
+ "original": "Rúa de Severino Cobas 118"
+ },
+ "latitude": 42.225468623,
+ "longitude": -8.691491081,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 14419,
+ "name": {
+ "original": "Estrada de Bembrive (cruce Camiño dos Rapadouros)"
+ },
+ "latitude": 42.201138734,
+ "longitude": -8.688585073,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 14420,
+ "name": {
+ "original": "Estrada de Bembrive 318"
+ },
+ "latitude": 42.201419233,
+ "longitude": -8.688526069,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 14421,
+ "name": {
+ "original": "Estrada das Prantas (fronte Campo de Béisbol)"
+ },
+ "latitude": 42.186683264,
+ "longitude": -8.669320703,
+ "lines": [
+ "15C"
+ ]
+ },
+ {
+ "stopId": 14422,
+ "name": {
+ "original": "Estrada das Plantas (Campo de Béisbol)"
+ },
+ "latitude": 42.18695315,
+ "longitude": -8.669514066,
+ "lines": [
+ "15C"
+ ]
+ },
+ {
+ "stopId": 14425,
+ "name": {
+ "original": "Avda. do Alcalde Gregorio Espino 2"
+ },
+ "latitude": 42.232253792,
+ "longitude": -8.707208575,
+ "lines": [
+ "31"
+ ]
+ },
+ {
+ "stopId": 14475,
+ "name": {
+ "original": "Rúa de Barcelona 78"
+ },
+ "latitude": 42.222992354,
+ "longitude": -8.728300382,
+ "lines": [
+ "C1"
+ ]
+ },
+ {
+ "stopId": 14890,
+ "name": {
+ "original": "Rúa das Teixugueiras 25"
+ },
+ "latitude": 42.207545331,
+ "longitude": -8.758718406,
+ "lines": [
+ "5B",
+ "13",
+ "N4",
+ "U1",
+ "H",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 14892,
+ "name": {
+ "original": "Rúa do Conde de Torrecedeira 123"
+ },
+ "latitude": 42.224929414,
+ "longitude": -8.735414067,
+ "lines": [
+ "C1",
+ "C3d",
+ "A",
+ "9B",
+ "15C",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 14893,
+ "name": {
+ "original": "Rúa de Manuel Costas Bastos 26"
+ },
+ "latitude": 42.243157956,
+ "longitude": -8.666962176,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 14894,
+ "name": {
+ "original": "Avda. do Alcalde Portanet 8"
+ },
+ "latitude": 42.211736934,
+ "longitude": -8.733337505,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 14895,
+ "name": {
+ "original": "Rúa do Areiro (cruce Camiño das Laxes)"
+ },
+ "latitude": 42.241392275,
+ "longitude": -8.681203235,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 14896,
+ "name": {
+ "original": "Rúa do Areiro 93"
+ },
+ "latitude": 42.241385532,
+ "longitude": -8.681400937,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 14897,
+ "name": {
+ "original": "Camiño do Arieiro (fronte 13)"
+ },
+ "latitude": 42.213239161,
+ "longitude": -8.67854147,
+ "lines": [
+ "31"
+ ]
+ },
+ {
+ "stopId": 14898,
+ "name": {
+ "original": "Camiño do Arieiro 13"
+ },
+ "latitude": 42.213239161,
+ "longitude": -8.678369808,
+ "lines": [
+ "31"
+ ]
+ },
+ {
+ "stopId": 14899,
+ "name": {
+ "original": "Rúa de López Mora 33"
+ },
+ "latitude": 42.225485719,
+ "longitude": -8.730501434,
+ "lines": [
+ "5A",
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 14900,
+ "name": {
+ "original": "Rúa de Martín Echegaray 24"
+ },
+ "latitude": 42.217196117,
+ "longitude": -8.743726669,
+ "lines": [
+ "23",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 14901,
+ "name": {
+ "original": "Avda. de Castelao 1"
+ },
+ "latitude": 42.220211003,
+ "longitude": -8.734183023,
+ "lines": [
+ "C3i",
+ "10",
+ "11",
+ "15A",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 14903,
+ "name": {
+ "original": "Rúa de Pi i Margall 66"
+ },
+ "latitude": 42.23174719,
+ "longitude": -8.731081308,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 14905,
+ "name": {
+ "original": "Camiño da Devesa (Cemiterio)"
+ },
+ "latitude": 42.249981353,
+ "longitude": -8.667186504,
+ "lines": [
+ "9B"
+ ]
+ },
+ {
+ "stopId": 14906,
+ "name": {
+ "original": "Rúa da Rabadeira 6"
+ },
+ "latitude": 42.232479787,
+ "longitude": -8.654890792,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 14907,
+ "name": {
+ "original": "Rúa da Rabadeira 24"
+ },
+ "latitude": 42.233655479,
+ "longitude": -8.653300242,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 14908,
+ "name": {
+ "original": "Rúa da Rabadeira 17"
+ },
+ "latitude": 42.233829075,
+ "longitude": -8.653458259,
+ "lines": [
+ "9B"
+ ]
+ },
+ {
+ "stopId": 14909,
+ "name": {
+ "original": "Rúa da Rabadeira 11"
+ },
+ "latitude": 42.232663198,
+ "longitude": -8.655097059,
+ "lines": [
+ "9B"
+ ]
+ },
+ {
+ "stopId": 14910,
+ "name": {
+ "original": "Estrada do Marco 4"
+ },
+ "latitude": 42.21025095,
+ "longitude": -8.704036986,
+ "lines": [
+ "18A",
+ "18B",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 14911,
+ "name": {
+ "original": "Estrada do Marco 16"
+ },
+ "latitude": 42.208830737,
+ "longitude": -8.706971174,
+ "lines": [
+ "18A",
+ "18B",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 15001,
+ "name": {
+ "original": "Rúa Regueiro do Forno (Vial C) Centro de Servicios"
+ },
+ "latitude": 42.176053629,
+ "longitude": -8.709460132,
+ "lines": [
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 15002,
+ "name": {
+ "original": "PTL 2"
+ },
+ "latitude": 42.177194637,
+ "longitude": -8.707850807,
+ "lines": [
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 15003,
+ "name": {
+ "original": "PTL 3"
+ },
+ "latitude": 42.178124939,
+ "longitude": -8.706606262,
+ "lines": [
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 15004,
+ "name": {
+ "original": "PTL 4"
+ },
+ "latitude": 42.176503017,
+ "longitude": -8.710007303,
+ "lines": [
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 20009,
+ "name": {
+ "original": "Estrada Herville 16"
+ },
+ "latitude": 42.154843231,
+ "longitude": -8.67357438,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 20010,
+ "name": {
+ "original": "Avda. de Balaídos 69"
+ },
+ "latitude": 42.212824845,
+ "longitude": -8.737161077,
+ "lines": [
+ "16",
+ "23",
+ "H"
+ ]
+ },
+ {
+ "stopId": 20011,
+ "name": {
+ "original": "Avda. de Balaídos 11"
+ },
+ "latitude": 42.213089061,
+ "longitude": -8.733392573,
+ "lines": [
+ "16",
+ "23",
+ "H"
+ ]
+ },
+ {
+ "stopId": 20012,
+ "name": {
+ "original": "Avda. de Castrelos 33"
+ },
+ "latitude": 42.215888032,
+ "longitude": -8.732331627,
+ "lines": [
+ "A",
+ "16",
+ "23",
+ "27",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 20013,
+ "name": {
+ "original": "Avda. de Castrelos 116"
+ },
+ "latitude": 42.215905917,
+ "longitude": -8.732471102,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "27",
+ "H2",
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 20018,
+ "name": {
+ "original": "Estrada Herville 70"
+ },
+ "latitude": 42.151451604,
+ "longitude": -8.673803367,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 20019,
+ "name": {
+ "original": "Subida aos Padróns 165"
+ },
+ "latitude": 42.149222193,
+ "longitude": -8.679363987,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 20020,
+ "name": {
+ "original": "Subida aos Padróns (cruce Parque Forestal)"
+ },
+ "latitude": 42.151606055,
+ "longitude": -8.679299082,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 20021,
+ "name": {
+ "original": "Subida aos Padróns (fronte 34)"
+ },
+ "latitude": 42.152770176,
+ "longitude": -8.686251828,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 20022,
+ "name": {
+ "original": "Rúa da Vía Norte (Hospital)"
+ },
+ "latitude": 42.234622237,
+ "longitude": -8.707758443,
+ "lines": [
+ "24"
+ ]
+ },
+ {
+ "stopId": 20023,
+ "name": {
+ "original": "Rúa da Vía Norte - Est. Intermodal - C.C."
+ },
+ "latitude": 42.234062973,
+ "longitude": -8.712195759,
+ "lines": [
+ "24"
+ ]
+ },
+ {
+ "stopId": 20024,
+ "name": {
+ "original": "Rúa das Teixugueiras 34"
+ },
+ "latitude": 42.207634066,
+ "longitude": -8.758920861,
+ "lines": [
+ "5B",
+ "13",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 20025,
+ "name": {
+ "original": "Rúa das Teixugueiras 38"
+ },
+ "latitude": 42.206553268,
+ "longitude": -8.760122491,
+ "lines": [
+ "5B",
+ "13",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 20026,
+ "name": {
+ "original": "Rúa das Teixugueiras 29"
+ },
+ "latitude": 42.206488366,
+ "longitude": -8.759906624,
+ "lines": [
+ "5B",
+ "13",
+ "N4",
+ "U1",
+ "H",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 20027,
+ "name": {
+ "original": "Avda. de Castelao 64"
+ },
+ "latitude": 42.217691983,
+ "longitude": -8.749585877,
+ "lines": [
+ "C3d",
+ "4A",
+ "4C",
+ "5B",
+ "10",
+ "12A",
+ "13",
+ "15A",
+ "N4",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 20029,
+ "name": {
+ "original": "Subida á Madroa (fronte Campo Fútbol)"
+ },
+ "latitude": 42.245921506,
+ "longitude": -8.673014474,
+ "lines": [
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 20030,
+ "name": {
+ "original": "Subida á Madroa (Campo Fútbol)"
+ },
+ "latitude": 42.247859379,
+ "longitude": -8.674363625,
+ "lines": [
+ "9B",
+ "28"
+ ]
+ },
+ {
+ "stopId": 20041,
+ "name": {
+ "original": "Rúa da Cabalaría 91"
+ },
+ "latitude": 42.233622103,
+ "longitude": -8.689209566,
+ "lines": [
+ "27",
+ "28"
+ ]
+ },
+ {
+ "stopId": 20042,
+ "name": {
+ "original": "Rúa da Cabalaría 148"
+ },
+ "latitude": 42.233723398,
+ "longitude": -8.689094231,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 20043,
+ "name": {
+ "original": "Rúa do Areiro 20"
+ },
+ "latitude": 42.236036786,
+ "longitude": -8.686656768,
+ "lines": [
+ "28"
+ ]
+ },
+ {
+ "stopId": 20044,
+ "name": {
+ "original": "Rúa de Martín Echegaray 7"
+ },
+ "latitude": 42.215220874,
+ "longitude": -8.742680967,
+ "lines": [
+ "23",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 20045,
+ "name": {
+ "original": "Rúa de Xestoso 4"
+ },
+ "latitude": 42.200532989,
+ "longitude": -8.674075447,
+ "lines": [
+ "15B"
+ ]
+ },
+ {
+ "stopId": 20046,
+ "name": {
+ "original": "Rúa de Xestoso 12"
+ },
+ "latitude": 42.201968444,
+ "longitude": -8.67477879,
+ "lines": [
+ "15B"
+ ]
+ },
+ {
+ "stopId": 20047,
+ "name": {
+ "original": "Rúa do Xestoso 72"
+ },
+ "latitude": 42.204330306,
+ "longitude": -8.674670483,
+ "lines": [
+ "15B"
+ ]
+ },
+ {
+ "stopId": 20048,
+ "name": {
+ "original": "Avda. de Cesáreo Vázquez (cruce Camiño Amariz Lourenzo)"
+ },
+ "latitude": 42.182684406,
+ "longitude": -8.802402364,
+ "lines": [
+ "11",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 20049,
+ "name": {
+ "original": "Avda. de Cesáreo Vázquez 62"
+ },
+ "latitude": 42.18238342,
+ "longitude": -8.802126069,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 20050,
+ "name": {
+ "original": "Rúa de Severino Cobas 186"
+ },
+ "latitude": 42.225550059,
+ "longitude": -8.686684563,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 20051,
+ "name": {
+ "original": "Rúa de Severino Cobas 89"
+ },
+ "latitude": 42.225652904,
+ "longitude": -8.686624017,
+ "lines": [
+ "25"
+ ]
+ },
+ {
+ "stopId": 20052,
+ "name": {
+ "original": "Rúa de Aragón 21"
+ },
+ "latitude": 42.232748414,
+ "longitude": -8.702539655,
+ "lines": [
+ "4A",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 20053,
+ "name": {
+ "original": "Rúa de Ángel de Lema (cruce Paraixal)"
+ },
+ "latitude": 42.248962858,
+ "longitude": -8.688272303,
+ "lines": [
+ "C3i",
+ "10"
+ ]
+ },
+ {
+ "stopId": 20054,
+ "name": {
+ "original": "Rúa de Ángel de Lema 33"
+ },
+ "latitude": 42.248897377,
+ "longitude": -8.689150714,
+ "lines": [
+ "C3d",
+ "10"
+ ]
+ },
+ {
+ "stopId": 20057,
+ "name": {
+ "original": "Estación Ferrocarril Guixar"
+ },
+ "latitude": 42.238843911,
+ "longitude": -8.713008504,
+ "lines": [
+ "A",
+ "5B",
+ "16",
+ "24"
+ ]
+ },
+ {
+ "stopId": 20058,
+ "name": {
+ "original": "Rúa do Canceleiro 6"
+ },
+ "latitude": 42.238435471,
+ "longitude": -8.714413687,
+ "lines": [
+ "5B",
+ "16"
+ ]
+ },
+ {
+ "stopId": 20059,
+ "name": {
+ "original": "Rúa de Manuel Álvarez (fronte 10)"
+ },
+ "latitude": 42.222745522,
+ "longitude": -8.677932515,
+ "lines": [
+ "25",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 20060,
+ "name": {
+ "original": "Rúa de Manuel Álvarez 10"
+ },
+ "latitude": 42.22282586,
+ "longitude": -8.678077606,
+ "lines": [
+ "25",
+ "31",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 20061,
+ "name": {
+ "original": "Rúa de Martín Echegaray (Colexio)"
+ },
+ "latitude": 42.217568173,
+ "longitude": -8.744018511,
+ "lines": [
+ "23",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 20062,
+ "name": {
+ "original": "Avda. de Beiramar 1"
+ },
+ "latitude": 42.236143706,
+ "longitude": -8.73180718,
+ "lines": [
+ "10",
+ "15B"
+ ]
+ },
+ {
+ "stopId": 20071,
+ "name": {
+ "original": "Rúa de Xestoso (fronte 105)"
+ },
+ "latitude": 42.205511653,
+ "longitude": -8.672824803,
+ "lines": [
+ "15B"
+ ]
+ },
+ {
+ "stopId": 20072,
+ "name": {
+ "original": "Camiño do Pouso"
+ },
+ "latitude": 42.196643694,
+ "longitude": -8.671663218,
+ "lines": [
+ "15B"
+ ]
+ },
+ {
+ "stopId": 20075,
+ "name": {
+ "original": "Avda. de Castelao 65"
+ },
+ "latitude": 42.218011215,
+ "longitude": -8.745369728,
+ "lines": [
+ "C3i",
+ "4A",
+ "4C",
+ "10",
+ "11",
+ "12A",
+ "15A",
+ "N1",
+ "N4",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 20076,
+ "name": {
+ "original": "Avda. de Castelao 25"
+ },
+ "latitude": 42.21901679,
+ "longitude": -8.739919147,
+ "lines": [
+ "C3i",
+ "4A",
+ "4C",
+ "10",
+ "11",
+ "12A",
+ "15A",
+ "N1",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 20077,
+ "name": {
+ "original": "Avda. de Castelao 40"
+ },
+ "latitude": 42.219259727,
+ "longitude": -8.739809435,
+ "lines": [
+ "C3d",
+ "4A",
+ "4C",
+ "5B",
+ "10",
+ "12A",
+ "13",
+ "15A",
+ "PSA 1",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 20078,
+ "name": {
+ "original": "Avda. das Camelias 3"
+ },
+ "latitude": 42.233341329,
+ "longitude": -8.728967219,
+ "lines": [
+ "4A",
+ "4C",
+ "11",
+ "12B",
+ "17",
+ "27",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 20079,
+ "name": {
+ "original": "Avda. das Camelias 8"
+ },
+ "latitude": 42.23341294,
+ "longitude": -8.729045156,
+ "lines": [
+ "4A",
+ "4C",
+ "7",
+ "12B",
+ "17",
+ "27",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 20080,
+ "name": {
+ "original": "Avda. de Santa Mariña 68"
+ },
+ "latitude": 42.221674556,
+ "longitude": -8.660937347,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 20081,
+ "name": {
+ "original": "Subida aos Padróns (cruce Camiño da Chan da Rabicha)"
+ },
+ "latitude": 42.151852858,
+ "longitude": -8.684956786,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 20082,
+ "name": {
+ "original": "Avda. de Santa Mariña (fronte 66)"
+ },
+ "latitude": 42.221758032,
+ "longitude": -8.661135597,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 20083,
+ "name": {
+ "original": "Rúa Castañal 6"
+ },
+ "latitude": 42.188074669,
+ "longitude": -8.701928367,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 20084,
+ "name": {
+ "original": "Rúa Castañal 26"
+ },
+ "latitude": 42.18711079,
+ "longitude": -8.699519743,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 20085,
+ "name": {
+ "original": "Rúa Castañal (cruce Camiño das Presas)"
+ },
+ "latitude": 42.185852445,
+ "longitude": -8.696410892,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 20086,
+ "name": {
+ "original": "Estrada dos Seixiños 67"
+ },
+ "latitude": 42.190645281,
+ "longitude": -8.696150583,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 20087,
+ "name": {
+ "original": "Estrada dos Seixiños 23"
+ },
+ "latitude": 42.194639373,
+ "longitude": -8.696795357,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 20089,
+ "name": {
+ "original": "Porriño - Padre Seixas ©"
+ },
+ "latitude": 42.213044566,
+ "longitude": -8.751396835,
+ "lines": [
+ "16"
+ ]
+ },
+ {
+ "stopId": 20091,
+ "name": {
+ "original": "Camiño da Miragaia 11-13"
+ },
+ "latitude": 42.238164803,
+ "longitude": -8.711212761,
+ "lines": [
+ "A",
+ "5B",
+ "16",
+ "24"
+ ]
+ },
+ {
+ "stopId": 20094,
+ "name": {
+ "original": "Rúa das Mantelas (fronte 63)"
+ },
+ "latitude": 42.22518736,
+ "longitude": -8.717399288,
+ "lines": [
+ "18A"
+ ]
+ },
+ {
+ "stopId": 20095,
+ "name": {
+ "original": "Estrada Vella de Madrid 107A"
+ },
+ "latitude": 42.219212419,
+ "longitude": -8.685836356,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 20096,
+ "name": {
+ "original": "Estrada Vella de Madrid (fronte 107A)"
+ },
+ "latitude": 42.219128991,
+ "longitude": -8.685753208,
+ "lines": [
+ "12A",
+ "12B",
+ "13",
+ "U2",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 20099,
+ "name": {
+ "original": "Rúa de Camilo Veiga 48"
+ },
+ "latitude": 42.222390674,
+ "longitude": -8.752507356,
+ "lines": [
+ "C3i",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 20100,
+ "name": {
+ "original": "Rúa de Camilo Veiga 6"
+ },
+ "latitude": 42.223195763,
+ "longitude": -8.749650702,
+ "lines": [
+ "C3i",
+ "15B",
+ "15C",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 20102,
+ "name": {
+ "original": "H. A. Cunqueiro (Porta Principal)"
+ },
+ "latitude": 42.191034002,
+ "longitude": -8.714303116,
+ "lines": [
+ "6",
+ "12B",
+ "18H",
+ "27",
+ "H1",
+ "H2",
+ "H3",
+ "H"
+ ]
+ },
+ {
+ "stopId": 20103,
+ "name": {
+ "original": "Avda. do Fragoso 21"
+ },
+ "latitude": 42.218946899,
+ "longitude": -8.733670293,
+ "lines": [
+ "7",
+ "12B",
+ "17",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 20104,
+ "name": {
+ "original": "Rúa de Emilia Pardo Bazán 134"
+ },
+ "latitude": 42.220938435,
+ "longitude": -8.709621883,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 20105,
+ "name": {
+ "original": "Rúa de Emilia Pardo Bazán 121"
+ },
+ "latitude": 42.221232035,
+ "longitude": -8.709808647,
+ "lines": [
+ "14"
+ ]
+ },
+ {
+ "stopId": 20107,
+ "name": {
+ "original": "Estrada do Porto (Lavadero)"
+ },
+ "latitude": 42.188244696,
+ "longitude": -8.703164368,
+ "lines": [
+ "6",
+ "27"
+ ]
+ },
+ {
+ "stopId": 20110,
+ "name": {
+ "original": "Rúa de Manuel Castro 10"
+ },
+ "latitude": 42.213797254,
+ "longitude": -8.741472696,
+ "lines": [
+ "23",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 20111,
+ "name": {
+ "original": "H. A. Cunqueiro (Hospital de Día)"
+ },
+ "latitude": 42.187585838,
+ "longitude": -8.716278919,
+ "lines": [
+ "A",
+ "6",
+ "12B",
+ "18H",
+ "27",
+ "H1",
+ "H2",
+ "H3",
+ "H"
+ ]
+ },
+ {
+ "stopId": 20112,
+ "name": {
+ "original": "H. A. Cunqueiro (Urxencias)"
+ },
+ "latitude": 42.188578188,
+ "longitude": -8.713087125,
+ "lines": [
+ "6",
+ "12B",
+ "18H",
+ "H1",
+ "H3",
+ "H"
+ ]
+ },
+ {
+ "stopId": 20113,
+ "name": {
+ "original": "Praza de América 3 (Dirección Hospital)"
+ },
+ "latitude": 42.220876566,
+ "longitude": -8.733367644,
+ "lines": [
+ "12B",
+ "H1",
+ "H2",
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 20114,
+ "name": {
+ "original": "Estrada do Porto (fronte Lavadero)"
+ },
+ "latitude": 42.18846205,
+ "longitude": -8.703352711,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 20115,
+ "name": {
+ "original": "Estrada do Porto (fronte cruce Rúa das Sueiras)"
+ },
+ "latitude": 42.190100441,
+ "longitude": -8.705453204,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 20116,
+ "name": {
+ "original": "Estrada da Coutada-Beade 2"
+ },
+ "latitude": 42.19202547,
+ "longitude": -8.705712064,
+ "lines": [
+ "6",
+ "27"
+ ]
+ },
+ {
+ "stopId": 20117,
+ "name": {
+ "original": "Estrada do Porto (cruce Camiño do Frascuelo)"
+ },
+ "latitude": 42.191616209,
+ "longitude": -8.706277831,
+ "lines": [
+ "6",
+ "27"
+ ]
+ },
+ {
+ "stopId": 20118,
+ "name": {
+ "original": "Rúa Conde de Gondomar"
+ },
+ "latitude": 42.228358488,
+ "longitude": -8.719490904,
+ "lines": [
+ "H2"
+ ]
+ },
+ {
+ "stopId": 20119,
+ "name": {
+ "original": "H. A. Cunqueiro (chegada)"
+ },
+ "latitude": 42.190930878,
+ "longitude": -8.71409354,
+ "lines": [
+ "6",
+ "12B",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 20124,
+ "name": {
+ "original": "Estrada Clara Campoamor 6"
+ },
+ "latitude": 42.208989468,
+ "longitude": -8.729330619,
+ "lines": [
+ "A",
+ "12B",
+ "U1",
+ "H1",
+ "H2",
+ "H",
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 20125,
+ "name": {
+ "original": "Estrada Clara Campoamor (fronte 6)"
+ },
+ "latitude": 42.209126911,
+ "longitude": -8.729344197,
+ "lines": [
+ "12B",
+ "H1",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 20126,
+ "name": {
+ "original": "Estrada Clara Campoamor (Rotonda HAC)"
+ },
+ "latitude": 42.190252452,
+ "longitude": -8.717998617,
+ "lines": [
+ "12B",
+ "18H",
+ "H1",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 20127,
+ "name": {
+ "original": "Estrada Clara Campoamor (fronte Rotonda HAC)"
+ },
+ "latitude": 42.19007538,
+ "longitude": -8.718125045,
+ "lines": [
+ "A",
+ "12B",
+ "18H",
+ "27",
+ "U1",
+ "H1",
+ "H2",
+ "H",
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 20130,
+ "name": {
+ "original": "Parque Forestal de Zamáns (Proba Andaina)"
+ },
+ "latitude": 42.152788309,
+ "longitude": -8.681902684,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 20132,
+ "name": {
+ "original": "Avda. de Galicia 341"
+ },
+ "latitude": 42.260473187,
+ "longitude": -8.67881466,
+ "lines": [
+ "C3i"
+ ]
+ },
+ {
+ "stopId": 20136,
+ "name": {
+ "original": "Avda. de E. Martínez Garrido 98"
+ },
+ "latitude": 42.225764699,
+ "longitude": -8.704499864,
+ "lines": [
+ "4C",
+ "23",
+ "31",
+ "N4",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 20137,
+ "name": {
+ "original": "Camiño da Devesa (Asociación Veciños)"
+ },
+ "latitude": 42.246563041,
+ "longitude": -8.669395817,
+ "lines": [
+ "9B"
+ ]
+ },
+ {
+ "stopId": 20139,
+ "name": {
+ "original": "Estrada Matamá Pazo (fronte 162)"
+ },
+ "latitude": 42.199144892,
+ "longitude": -8.758506717,
+ "lines": [
+ "29"
+ ]
+ },
+ {
+ "stopId": 20141,
+ "name": {
+ "original": "Avda. da Ponte (fronte Vigo Memorial)"
+ },
+ "latitude": 42.21057897,
+ "longitude": -8.671171189,
+ "lines": [
+ "12B",
+ "15B",
+ "15C"
+ ]
+ },
+ {
+ "stopId": 20142,
+ "name": {
+ "original": "Camiño do Outeiro 3"
+ },
+ "latitude": 42.200738188,
+ "longitude": -8.714882876,
+ "lines": [
+ "18B"
+ ]
+ },
+ {
+ "stopId": 20143,
+ "name": {
+ "original": "Rúa das Teixugueiras (fronte 1)"
+ },
+ "latitude": 42.215448094,
+ "longitude": -8.756474306,
+ "lines": [
+ "15A"
+ ]
+ },
+ {
+ "stopId": 20154,
+ "name": {
+ "original": "Rúa de Ramiro Pascual (fronte 127)"
+ },
+ "latitude": 42.192089689,
+ "longitude": -8.709245389,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 20155,
+ "name": {
+ "original": "Rúa de Ramiro Pascual 131"
+ },
+ "latitude": 42.19217626,
+ "longitude": -8.708899009,
+ "lines": [
+ "27"
+ ]
+ },
+ {
+ "stopId": 20156,
+ "name": {
+ "original": "Económicas e Empresariais (CUVI 2)"
+ },
+ "latitude": 42.169602007,
+ "longitude": -8.680122554,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 20157,
+ "name": {
+ "original": "Estrada do Porto 88"
+ },
+ "latitude": 42.185615419,
+ "longitude": -8.702424678,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 20158,
+ "name": {
+ "original": "Estrada do Porto 81"
+ },
+ "latitude": 42.185593055,
+ "longitude": -8.702377974,
+ "lines": [
+ "6"
+ ]
+ },
+ {
+ "stopId": 20159,
+ "name": {
+ "original": "Estrada de Valadares 571"
+ },
+ "latitude": 42.160348044,
+ "longitude": -8.718706355,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 20160,
+ "name": {
+ "original": "Estrada de Valadares 522"
+ },
+ "latitude": 42.160066796,
+ "longitude": -8.718938239,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 20166,
+ "name": {
+ "original": "Camiño da Brea 2"
+ },
+ "latitude": 42.202134841,
+ "longitude": -8.70572793,
+ "lines": [
+ "18A",
+ "18B"
+ ]
+ },
+ {
+ "stopId": 20167,
+ "name": {
+ "original": "Camiño da Brea 3"
+ },
+ "latitude": 42.202095058,
+ "longitude": -8.705814233,
+ "lines": [
+ "18A",
+ "18B"
+ ]
+ },
+ {
+ "stopId": 20168,
+ "name": {
+ "original": "Estrada do Freixo (despois 118)"
+ },
+ "latitude": 42.173596087,
+ "longitude": -8.730918928,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 20169,
+ "name": {
+ "original": "Estrada do Freixo (despois 235)"
+ },
+ "latitude": 42.173616782,
+ "longitude": -8.730810863,
+ "lines": [
+ "7"
+ ]
+ },
+ {
+ "stopId": 20170,
+ "name": {
+ "original": "Rúa de Álvaro Cunqueiro 4"
+ },
+ "latitude": 42.224544805,
+ "longitude": -8.730413561,
+ "lines": [
+ "5A",
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 20171,
+ "name": {
+ "original": "Estrada Clara Campoamor (cruce Camiño da Pousa)"
+ },
+ "latitude": 42.204380762,
+ "longitude": -8.726688445,
+ "lines": [
+ "12B",
+ "H1",
+ "H2"
+ ]
+ },
+ {
+ "stopId": 20172,
+ "name": {
+ "original": "Estrada Clara Campoamor (cruce Camiño da Nogueira)"
+ },
+ "latitude": 42.203736336,
+ "longitude": -8.726617869,
+ "lines": [
+ "A",
+ "12B",
+ "U1",
+ "H1",
+ "H2",
+ "H",
+ "PTL"
+ ]
+ },
+ {
+ "stopId": 20173,
+ "name": {
+ "original": "Avda. de Castrelos 502"
+ },
+ "latitude": 42.192504056,
+ "longitude": -8.721215121,
+ "lines": [
+ "7",
+ "U1"
+ ]
+ },
+ {
+ "stopId": 20174,
+ "name": {
+ "original": "Baixada ao Pontillón S/N"
+ },
+ "latitude": 42.21519917,
+ "longitude": -8.726793773,
+ "lines": [
+ "A"
+ ]
+ },
+ {
+ "stopId": 20177,
+ "name": {
+ "original": "Rúa de Pizarro 16"
+ },
+ "latitude": 42.230767817,
+ "longitude": -8.715105964,
+ "lines": [
+ "C3i",
+ "6",
+ "11",
+ "15A",
+ "23",
+ "25",
+ "28"
+ ]
+ },
+ {
+ "stopId": 20178,
+ "name": {
+ "original": "Estrada de Camposancos (cruce Camiño da Estea)"
+ },
+ "latitude": 42.172412443,
+ "longitude": -8.799591567,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 20180,
+ "name": {
+ "original": "Rúa do Reiseñor 10"
+ },
+ "latitude": 42.229527407,
+ "longitude": -8.70843784,
+ "lines": [
+ "H2"
+ ]
+ },
+ {
+ "stopId": 20186,
+ "name": {
+ "original": "Rúa da Rabadeira 71"
+ },
+ "latitude": 42.23755404,
+ "longitude": -8.651558138,
+ "lines": [
+ "9B"
+ ]
+ },
+ {
+ "stopId": 20187,
+ "name": {
+ "original": "Rúa da Rabadeira 46"
+ },
+ "latitude": 42.237422128,
+ "longitude": -8.65153195,
+ "lines": [
+ "9B",
+ "27"
+ ]
+ },
+ {
+ "stopId": 20188,
+ "name": {
+ "original": "Rúa da Saa (fronte 43)"
+ },
+ "latitude": 42.201670402,
+ "longitude": -8.708928464,
+ "lines": [
+ "18B",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 20189,
+ "name": {
+ "original": "Rúa da Saa 10"
+ },
+ "latitude": 42.201625853,
+ "longitude": -8.712945043,
+ "lines": [
+ "18B",
+ "H3"
+ ]
+ },
+ {
+ "stopId": 20190,
+ "name": {
+ "original": "Avda. das Camelias (fronte Praza do Rei)"
+ },
+ "latitude": 42.234906013,
+ "longitude": -8.72662052,
+ "lines": [
+ "4A",
+ "4C",
+ "11",
+ "12B",
+ "17",
+ "27",
+ "N1"
+ ]
+ },
+ {
+ "stopId": 20191,
+ "name": {
+ "original": "Rúa das Figueiras 200"
+ },
+ "latitude": 42.229676205,
+ "longitude": -8.657383392,
+ "lines": [
+ "15A",
+ "25"
+ ]
+ },
+ {
+ "stopId": 20192,
+ "name": {
+ "original": "Rúa de Colón 26"
+ },
+ "latitude": 42.237168511,
+ "longitude": -8.720373767,
+ "lines": [
+ "4A",
+ "4C",
+ "5B",
+ "7",
+ "12B",
+ "16",
+ "17",
+ "PSA 4"
+ ]
+ },
+ {
+ "stopId": 20193,
+ "name": {
+ "original": "Rúa de Policarpo Sanz 25"
+ },
+ "latitude": 42.23767601188501,
+ "longitude": -8.721582630122455,
+ "lines": [
+ "C3i",
+ "A",
+ "5A",
+ "5B",
+ "10",
+ "11",
+ "15B",
+ "15C",
+ "N1",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 20194,
+ "name": {
+ "original": "Rúa de Cánovas del Castillo 28"
+ },
+ "latitude": 42.240364985,
+ "longitude": -8.724530974,
+ "lines": [
+ "C1",
+ "C3d",
+ "A",
+ "5A",
+ "9B",
+ "10",
+ "15B",
+ "15C",
+ "28",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 20195,
+ "name": {
+ "original": "Praza de Compostela (fronte 35)"
+ },
+ "latitude": 42.2393606,
+ "longitude": -8.724131464,
+ "lines": [
+ "C3i",
+ "A",
+ "5A",
+ "5B",
+ "6",
+ "9B",
+ "10",
+ "11",
+ "15B",
+ "15C",
+ "28",
+ "N1",
+ "N4",
+ "H1"
+ ]
+ },
+ {
+ "stopId": 20196,
+ "name": {
+ "original": "Estrada de Camposancos 498"
+ },
+ "latitude": 42.175325155,
+ "longitude": -8.799594139,
+ "lines": [
+ "12A"
+ ]
+ },
+ {
+ "stopId": 20197,
+ "name": {
+ "original": "Rúa de Pi i Margall 3-5"
+ },
+ "latitude": 42.23558703,
+ "longitude": -8.728830897,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 20198,
+ "name": {
+ "original": "Rúa de Policarpo Sanz 26"
+ },
+ "latitude": 42.237533428,
+ "longitude": -8.722195046,
+ "lines": [
+ "C1",
+ "C3d",
+ "A",
+ "5A",
+ "9B",
+ "10",
+ "15B",
+ "15C",
+ "24",
+ "28",
+ "N4"
+ ]
+ },
+ {
+ "stopId": 20199,
+ "name": {
+ "original": "Rúa de Puerto Rico 12"
+ },
+ "latitude": 42.228802205,
+ "longitude": -8.718136653,
+ "lines": [
+ "H2"
+ ]
+ },
+ {
+ "stopId": 20200,
+ "name": {
+ "original": "Rúa de Pi i Margall (fronte 5)"
+ },
+ "latitude": 42.235482452,
+ "longitude": -8.728981431,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 20201,
+ "name": {
+ "original": "Paseo de Granada S/N"
+ },
+ "latitude": 42.235701104,
+ "longitude": -8.726054911,
+ "lines": [
+ "5B",
+ "12A"
+ ]
+ },
+ {
+ "stopId": 20203,
+ "name": {
+ "original": "Avda. da Gran Vía 47"
+ },
+ "latitude": 42.230881062,
+ "longitude": -8.718397577,
+ "lines": [
+ "7",
+ "12B",
+ "14",
+ "16",
+ "18A",
+ "18B",
+ "18H"
+ ]
+ },
+ {
+ "stopId": 20209,
+ "name": {
+ "original": "Avda. do Alcalde Portanet 23"
+ },
+ "latitude": 42.211481651,
+ "longitude": -8.734440746,
+ "lines": [
+ "H1"
+ ]
+ },
+ {
+ "stopId": 20210,
+ "name": {
+ "original": "Estrada de Camposancos 108"
+ },
+ "latitude": 42.19824056,
+ "longitude": -8.763182189,
+ "lines": [
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 20211,
+ "name": {
+ "original": "Estrada de Camposancos 109"
+ },
+ "latitude": 42.198422825,
+ "longitude": -8.762538026,
+ "lines": [
+ "11",
+ "29"
+ ]
+ },
+ {
+ "stopId": 20212,
+ "name": {
+ "original": "Rúa do Canabido 18"
+ },
+ "latitude": 42.188388732,
+ "longitude": -8.805956864,
+ "lines": [
+ "10"
+ ]
+ },
+ {
+ "stopId": 20215,
+ "name": {
+ "original": "Rúa da Coruña 21"
+ },
+ "latitude": 42.223880296,
+ "longitude": -8.735520196,
+ "lines": [
+ "A",
+ "5A",
+ "5B",
+ "10",
+ "11",
+ "13",
+ "N4",
+ "U1",
+ "H1",
+ "H"
+ ]
+ },
+ {
+ "stopId": 20216,
+ "name": {
+ "original": "Avda. de Cesáreo Vázquez (fronte 43)"
+ },
+ "latitude": 42.179747589,
+ "longitude": -8.802157388,
+ "lines": [
+ "11"
+ ]
+ },
+ {
+ "stopId": 20219,
+ "name": {
+ "original": "Avda. do Aeroporto (fronte 90)"
+ },
+ "latitude": 42.234830699,
+ "longitude": -8.695443515,
+ "lines": [
+ "A",
+ "9B",
+ "27",
+ "28"
+ ]
+ }
+]
diff --git a/src/frontend/public/sw.js b/src/frontend/public/sw.js
new file mode 100644
index 0000000..70ca169
--- /dev/null
+++ b/src/frontend/public/sw.js
@@ -0,0 +1,51 @@
+const API_CACHE_NAME = 'api-cache-v1'
+const API_URL_PATTERN = /\/api\/(GetStopList)/;
+const API_MAX_AGE = 24 * 60 * 60 * 1000; // 24 hours
+
+self.addEventListener('install', (event) => {
+ event.waitUntil(self.skipWaiting());
+});
+
+self.addEventListener('activate', (event) => {
+ event.waitUntil(self.clients.claim());
+});
+
+self.addEventListener('fetch', async (event) => {
+ const url = new URL(event.request.url);
+
+ if (event.request.method !== "GET" || !API_URL_PATTERN.test(url.pathname)) {
+ return;
+ }
+
+ event.respondWith(apiCacheFirst(event.request));
+});
+
+async function apiCacheFirst(request) {
+ const cache = await caches.open(API_CACHE_NAME);
+ const cachedResponse = await cache.match(request);
+
+ if (cachedResponse) {
+ const age = Date.now() - new Date(cachedResponse.headers.get('date')).getTime();
+ if (age < API_MAX_AGE) {
+ console.debug(`SW: Cache HIT for ${request.url}`);
+ return cachedResponse;
+ }
+
+ // Cache is too old, fetch a fresh copy
+ cache.delete(request);
+ }
+
+ try {
+ const netResponse = await fetch(request);
+
+ const responseToCache = netResponse.clone();
+
+ cache.put(request, responseToCache);
+
+ console.debug(`SW: Cache MISS for ${request.url}`);
+
+ return netResponse;
+ } catch (error) {
+ throw error;
+ }
+} \ No newline at end of file
diff --git a/src/frontend/src/AppContext.tsx b/src/frontend/src/AppContext.tsx
new file mode 100644
index 0000000..ecba9e2
--- /dev/null
+++ b/src/frontend/src/AppContext.tsx
@@ -0,0 +1,234 @@
+/* eslint-disable react-refresh/only-export-components */
+import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
+import { type LatLngTuple } from 'leaflet';
+
+type Theme = 'light' | 'dark';
+type TableStyle = 'regular'|'grouped';
+type MapPositionMode = 'gps' | 'last';
+
+interface MapState {
+ center: LatLngTuple;
+ zoom: number;
+ userLocation: LatLngTuple | null;
+ hasLocationPermission: boolean;
+}
+
+interface AppContextProps {
+ theme: Theme;
+ setTheme: React.Dispatch<React.SetStateAction<Theme>>;
+ toggleTheme: () => void;
+
+ tableStyle: TableStyle;
+ setTableStyle: React.Dispatch<React.SetStateAction<TableStyle>>;
+ toggleTableStyle: () => void;
+
+ mapState: MapState;
+ setMapCenter: (center: LatLngTuple) => void;
+ setMapZoom: (zoom: number) => void;
+ setUserLocation: (location: LatLngTuple | null) => void;
+ setLocationPermission: (hasPermission: boolean) => void;
+ updateMapState: (center: LatLngTuple, zoom: number) => void;
+
+ mapPositionMode: MapPositionMode;
+ setMapPositionMode: (mode: MapPositionMode) => void;
+}
+
+// Coordenadas por defecto centradas en Vigo
+const DEFAULT_CENTER: LatLngTuple = [42.229188855975046, -8.72246955783102];
+const DEFAULT_ZOOM = 14;
+
+const AppContext = createContext<AppContextProps | undefined>(undefined);
+
+export const AppProvider = ({ children }: { children: ReactNode }) => {
+ //#region Theme
+ const [theme, setTheme] = useState<Theme>(() => {
+ const savedTheme = localStorage.getItem('theme');
+ if (savedTheme) {
+ return savedTheme as Theme;
+ }
+ const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
+ return prefersDark ? 'dark' : 'light';
+ });
+
+ const toggleTheme = () => {
+ setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
+ };
+
+ useEffect(() => {
+ document.documentElement.setAttribute('data-theme', theme);
+ localStorage.setItem('theme', theme);
+ }, [theme]);
+ //#endregion
+
+ //#region Table Style
+ const [tableStyle, setTableStyle] = useState<TableStyle>(() => {
+ const savedTableStyle = localStorage.getItem('tableStyle');
+ if (savedTableStyle) {
+ return savedTableStyle as TableStyle;
+ }
+ return 'regular';
+ });
+
+ const toggleTableStyle = () => {
+ setTableStyle((prevTableStyle) => (prevTableStyle === 'regular' ? 'grouped' : 'regular'));
+ }
+
+ useEffect(() => {
+ localStorage.setItem('tableStyle', tableStyle);
+ }, [tableStyle]);
+ //#endregion
+
+ //#region Map Position Mode
+ const [mapPositionMode, setMapPositionMode] = useState<MapPositionMode>(() => {
+ const saved = localStorage.getItem('mapPositionMode');
+ return saved === 'last' ? 'last' : 'gps';
+ });
+
+ useEffect(() => {
+ localStorage.setItem('mapPositionMode', mapPositionMode);
+ }, [mapPositionMode]);
+ //#endregion
+
+ //#region Map State
+ const [mapState, setMapState] = useState<MapState>(() => {
+ const savedMapState = localStorage.getItem('mapState');
+ if (savedMapState) {
+ try {
+ const parsed = JSON.parse(savedMapState);
+ return {
+ center: parsed.center || DEFAULT_CENTER,
+ zoom: parsed.zoom || DEFAULT_ZOOM,
+ userLocation: parsed.userLocation || null,
+ hasLocationPermission: parsed.hasLocationPermission || false
+ };
+ } catch (e) {
+ console.error('Error parsing saved map state', e);
+ }
+ }
+ return {
+ center: DEFAULT_CENTER,
+ zoom: DEFAULT_ZOOM,
+ userLocation: null,
+ hasLocationPermission: false
+ };
+ });
+
+ // Helper: check if coordinates are within Vigo bounds
+ function isWithinVigo([lat, lng]: LatLngTuple): boolean {
+ // Rough bounding box for Vigo
+ return lat >= 42.18 && lat <= 42.30 && lng >= -8.78 && lng <= -8.65;
+ }
+
+ // On app load, if mapPositionMode is 'gps', try to get GPS and set map center
+ useEffect(() => {
+ if (mapPositionMode === 'gps') {
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(
+ (position) => {
+ const { latitude, longitude } = position.coords;
+ const coords: LatLngTuple = [latitude, longitude];
+ if (isWithinVigo(coords)) {
+ setMapState(prev => {
+ const newState = { ...prev, center: coords, zoom: 16, userLocation: coords };
+ localStorage.setItem('mapState', JSON.stringify(newState));
+ return newState;
+ });
+ }
+ },
+ () => {
+ // Ignore error, fallback to last
+ }
+ );
+ }
+ }
+ // If 'last', do nothing (already loaded from localStorage)
+ }, [mapPositionMode]);
+
+ const setMapCenter = (center: LatLngTuple) => {
+ setMapState(prev => {
+ const newState = { ...prev, center };
+ localStorage.setItem('mapState', JSON.stringify(newState));
+ return newState;
+ });
+ };
+
+ const setMapZoom = (zoom: number) => {
+ setMapState(prev => {
+ const newState = { ...prev, zoom };
+ localStorage.setItem('mapState', JSON.stringify(newState));
+ return newState;
+ });
+ };
+
+ const setUserLocation = (userLocation: LatLngTuple | null) => {
+ setMapState(prev => {
+ const newState = { ...prev, userLocation };
+ localStorage.setItem('mapState', JSON.stringify(newState));
+ return newState;
+ });
+ };
+
+ const setLocationPermission = (hasLocationPermission: boolean) => {
+ setMapState(prev => {
+ const newState = { ...prev, hasLocationPermission };
+ localStorage.setItem('mapState', JSON.stringify(newState));
+ return newState;
+ });
+ };
+
+ const updateMapState = (center: LatLngTuple, zoom: number) => {
+ setMapState(prev => {
+ const newState = { ...prev, center, zoom };
+ localStorage.setItem('mapState', JSON.stringify(newState));
+ return newState;
+ });
+ };
+ //#endregion
+
+ // Tratar de obtener la ubicación del usuario cuando se carga la aplicación si ya se había concedido permiso antes
+ useEffect(() => {
+ if (mapState.hasLocationPermission && !mapState.userLocation) {
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(
+ (position) => {
+ const { latitude, longitude } = position.coords;
+ setUserLocation([latitude, longitude]);
+ },
+ (error) => {
+ console.error('Error getting location:', error);
+ setLocationPermission(false);
+ }
+ );
+ }
+ }
+ }, [mapState.hasLocationPermission, mapState.userLocation]);
+
+ return (
+ <AppContext.Provider value={{
+ theme,
+ setTheme,
+ toggleTheme,
+ tableStyle,
+ setTableStyle,
+ toggleTableStyle,
+ mapState,
+ setMapCenter,
+ setMapZoom,
+ setUserLocation,
+ setLocationPermission,
+ updateMapState,
+ mapPositionMode,
+ setMapPositionMode
+ }}>
+ {children}
+ </AppContext.Provider>
+ );
+};
+
+export const useApp = () => {
+ const context = useContext(AppContext);
+ if (!context) {
+ throw new Error('useApp must be used within a AppProvider');
+ }
+ return context;
+};
diff --git a/src/frontend/src/ErrorBoundary.tsx b/src/frontend/src/ErrorBoundary.tsx
new file mode 100644
index 0000000..5c877b7
--- /dev/null
+++ b/src/frontend/src/ErrorBoundary.tsx
@@ -0,0 +1,46 @@
+import React, { Component, type ReactNode } from 'react';
+
+interface ErrorBoundaryProps {
+ children: ReactNode;
+}
+
+interface ErrorBoundaryState {
+ hasError: boolean;
+ error: Error | null;
+}
+
+class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
+ constructor(props: ErrorBoundaryProps) {
+ super(props);
+ this.state = {
+ hasError: false,
+ error: null
+ };
+ }
+
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
+ return {
+ hasError: true,
+ error
+ };
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+ console.error("Uncaught error:", error, errorInfo);
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return <>
+ <h1>Something went wrong.</h1>
+ <pre>
+ {this.state.error?.stack}
+ </pre>
+ </>;
+ }
+
+ return this.props.children;
+ }
+}
+
+export default ErrorBoundary;
diff --git a/src/frontend/src/Layout.css b/src/frontend/src/Layout.css
new file mode 100644
index 0000000..601794b
--- /dev/null
+++ b/src/frontend/src/Layout.css
@@ -0,0 +1,60 @@
+#root {
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ width: 100%;
+ overflow: hidden;
+}
+
+.main-content {
+ flex: 1;
+ overflow: auto;
+ padding-bottom: 60px; /* Extra padding to ensure content isn't hidden behind navbar */
+}
+
+.nav-bar {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 5;
+
+ background-color: var(--background-color);
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ height: 60px;
+ border-top: 1px solid var(--border-color);
+ box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
+}
+
+.nav-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 8px;
+ color: #616161;
+ text-decoration: none;
+ width: 33.3%;
+ font-size: 14px;
+}
+
+.nav-item.active {
+ color: var(--button-background-color);
+}
+
+.theme-toggle {
+ background: none;
+ border: none;
+ cursor: pointer;
+ color: inherit;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 8px;
+}
+
+.theme-toggle:hover {
+ color: var(--button-hover-background-color);
+}
diff --git a/src/frontend/src/Layout.tsx b/src/frontend/src/Layout.tsx
new file mode 100644
index 0000000..f933ddc
--- /dev/null
+++ b/src/frontend/src/Layout.tsx
@@ -0,0 +1,55 @@
+import { type ReactNode } from 'react';
+import { Link, useLocation } from 'react-router';
+import { MapPin, Map, Settings } from 'lucide-react';
+import './Layout.css';
+
+interface LayoutProps {
+ children: ReactNode;
+}
+
+export function Layout({ children }: LayoutProps) {
+ const location = useLocation();
+
+ const navItems = [
+ {
+ name: 'Paradas',
+ icon: MapPin,
+ path: '/stops'
+ },
+ {
+ name: 'Mapa',
+ icon: Map,
+ path: '/map'
+ },
+ {
+ name: 'Ajustes',
+ icon: Settings,
+ path: '/settings'
+ }
+ ];
+
+ return (
+ <>
+ <main className="main-content">
+ {children}
+ </main>
+ <nav className="nav-bar">
+ {navItems.map(item => {
+ const Icon = item.icon;
+ const isActive = location.pathname.startsWith(item.path);
+
+ return (
+ <Link
+ key={item.name}
+ to={item.path}
+ className={`nav-item ${isActive ? 'active' : ''}`}
+ >
+ <Icon size={24} />
+ <span>{item.name}</span>
+ </Link>
+ );
+ })}
+ </nav>
+ </>
+ );
+}
diff --git a/src/frontend/src/components/GroupedTable.tsx b/src/frontend/src/components/GroupedTable.tsx
new file mode 100644
index 0000000..b7f990d
--- /dev/null
+++ b/src/frontend/src/components/GroupedTable.tsx
@@ -0,0 +1,74 @@
+import { type StopDetails } from "../pages/Estimates";
+import LineIcon from "./LineIcon";
+
+interface GroupedTable {
+ data: StopDetails;
+ dataDate: Date | null;
+}
+
+export const GroupedTable: React.FC<GroupedTable> = ({ data, dataDate }) => {
+ const formatDistance = (meters: number) => {
+ if (meters > 1024) {
+ return `${(meters / 1000).toFixed(1)} km`;
+ } else {
+ return `${meters} m`;
+ }
+ }
+
+ const groupedEstimates = data.estimates.reduce((acc, estimate) => {
+ if (!acc[estimate.line]) {
+ acc[estimate.line] = [];
+ }
+ acc[estimate.line].push(estimate);
+ return acc;
+ }, {} as Record<string, typeof data.estimates>);
+
+ const sortedLines = Object.keys(groupedEstimates).sort((a, b) => {
+ const firstArrivalA = groupedEstimates[a][0].minutes;
+ const firstArrivalB = groupedEstimates[b][0].minutes;
+ return firstArrivalA - firstArrivalB;
+ });
+
+ return <table className="table">
+ <caption>Estimaciones de llegadas a las {dataDate?.toLocaleTimeString()}</caption>
+
+ <thead>
+ <tr>
+ <th>Línea</th>
+ <th>Ruta</th>
+ <th>Llegada</th>
+ <th>Distancia</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {sortedLines.map((line) => (
+ groupedEstimates[line].map((estimate, idx) => (
+ <tr key={`${line}-${idx}`}>
+ {idx === 0 && (
+ <td rowSpan={groupedEstimates[line].length}>
+ <LineIcon line={line} />
+ </td>
+ )}
+ <td>{estimate.route}</td>
+ <td>{`${estimate.minutes} min`}</td>
+ <td>
+ {estimate.meters > -1
+ ? formatDistance(estimate.meters)
+ : "No disponible"
+ }
+ </td>
+ </tr>
+ ))
+ ))}
+ </tbody>
+
+ {data?.estimates.length === 0 && (
+ <tfoot>
+ <tr>
+ <td colSpan={4}>No hay estimaciones disponibles</td>
+ </tr>
+ </tfoot>
+ )}
+ </table>
+}
diff --git a/src/frontend/src/components/LineIcon.css b/src/frontend/src/components/LineIcon.css
new file mode 100644
index 0000000..e7e8949
--- /dev/null
+++ b/src/frontend/src/components/LineIcon.css
@@ -0,0 +1,239 @@
+:root {
+ --line-c1: rgb(237, 71, 19);
+ --line-c3d: rgb(255, 204, 0);
+ --line-c3i: rgb(255, 204, 0);
+ --line-l4a: rgb(0, 153, 0);
+ --line-l4c: rgb(0, 153, 0);
+ --line-l5a: rgb(0, 176, 240);
+ --line-l5b: rgb(0, 176, 240);
+ --line-l6: rgb(204, 51, 153);
+ --line-l7: rgb(150, 220, 153);
+ --line-l9b: rgb(244, 202, 140);
+ --line-l10: rgb(153, 51, 0);
+ --line-l11: rgb(226, 0, 38);
+ --line-l12a: rgb(106, 150, 190);
+ --line-l12b: rgb(106, 150, 190);
+ --line-l13: rgb(0, 176, 240);
+ --line-l14: rgb(129, 142, 126);
+ --line-l15a: rgb(216, 168, 206);
+ --line-l15b: rgb(216, 168, 206);
+ --line-l15c: rgb(216, 168, 168);
+ --line-l16: rgb(129, 142, 126);
+ --line-l17: rgb(214, 245, 31);
+ --line-l18a: rgb(212, 80, 168);
+ --line-l18b: rgb(0, 0, 0);
+ --line-l18h: rgb(0, 0, 0);
+ --line-l23: rgb(0, 70, 210);
+ --line-l24: rgb(191, 191, 191);
+ --line-l25: rgb(172, 100, 4);
+ --line-l27: rgb(112, 74, 42);
+ --line-l28: rgb(176, 189, 254);
+ --line-l29: rgb(248, 184, 90);
+ --line-l31: rgb(255, 255, 0);
+ --line-a: rgb(119, 41, 143);
+ --line-h: rgb(0, 96, 168);
+ --line-h1: rgb(0, 96, 168);
+ --line-h2: rgb(0, 96, 168);
+ --line-h3: rgb(0, 96, 168);
+ --line-lzd: rgb(61, 78, 167);
+ --line-n1: rgb(191, 191, 191);
+ --line-n4: rgb(102, 51, 102);
+ --line-psa1: rgb(0, 153, 0);
+ --line-psa4: rgb(0, 153, 0);
+ --line-ptl: rgb(150, 220, 153);
+ --line-turistico: rgb(102, 51, 102);
+ --line-u1: rgb(172, 100, 4);
+ --line-u2: rgb(172, 100, 4);
+}
+
+.line-icon {
+ display: inline-block;
+ padding: 0.25rem 0.5rem;
+ margin-right: 0.5rem;
+ border-bottom: 3px solid;
+ font-size: 0.9rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ color: inherit;
+ /* Prevent color change on hover */
+}
+
+.line-c1 {
+ border-color: var(--line-c1);
+}
+
+.line-c3d {
+ border-color: var(--line-c3d);
+}
+
+.line-c3i {
+ border-color: var(--line-c3i);
+}
+
+.line-l4a {
+ border-color: var(--line-l4a);
+}
+
+.line-l4c {
+ border-color: var(--line-l4c);
+}
+
+.line-l5a {
+ border-color: var(--line-l5a);
+}
+
+.line-l5b {
+ border-color: var(--line-l5b);
+}
+
+.line-l6 {
+ border-color: var(--line-l6);
+}
+
+.line-l7 {
+ border-color: var(--line-l7);
+}
+
+.line-l9b {
+ border-color: var(--line-l9b);
+}
+
+.line-l10 {
+ border-color: var(--line-l10);
+}
+
+.line-l11 {
+ border-color: var(--line-l11);
+}
+
+.line-l12a {
+ border-color: var(--line-l12a);
+}
+
+.line-l12b {
+ border-color: var(--line-l12b);
+}
+
+.line-l13 {
+ border-color: var(--line-l13);
+}
+
+.line-l14 {
+ border-color: var(--line-l14);
+}
+
+.line-l15a {
+ border-color: var(--line-l15a);
+}
+
+.line-l15b {
+ border-color: var(--line-l15b);
+}
+
+.line-l15c {
+ border-color: var(--line-l15c);
+}
+
+.line-l16 {
+ border-color: var(--line-l16);
+}
+
+.line-l17 {
+ border-color: var(--line-l17);
+}
+
+.line-l18a {
+ border-color: var(--line-l18a);
+}
+
+.line-l18b {
+ border-color: var(--line-l18b);
+}
+
+.line-l18h {
+ border-color: var(--line-l18h);
+}
+
+.line-l23 {
+ border-color: var(--line-l23);
+}
+
+.line-l24 {
+ border-color: var(--line-l24);
+}
+
+.line-l25 {
+ border-color: var(--line-l25);
+}
+
+.line-l27 {
+ border-color: var(--line-l27);
+}
+
+.line-l28 {
+ border-color: var(--line-l28);
+}
+
+.line-l29 {
+ border-color: var(--line-l29);
+}
+
+.line-l31 {
+ border-color: var(--line-l31);
+}
+
+.line-a {
+ border-color: var(--line-a);
+}
+
+.line-h {
+ border-color: var(--line-h);
+}
+
+.line-h1 {
+ border-color: var(--line-h1);
+}
+
+.line-h2 {
+ border-color: var(--line-h2);
+}
+
+.line-h3 {
+ border-color: var(--line-h3);
+}
+
+.line-lzd {
+ border-color: var(--line-lzd);
+}
+
+.line-n1 {
+ border-color: var(--line-n1);
+}
+
+.line-n4 {
+ border-color: var(--line-n4);
+}
+
+.line-psa1 {
+ border-color: var(--line-psa1);
+}
+
+.line-psa4 {
+ border-color: var(--line-psa4);
+}
+
+.line-ptl {
+ border-color: var(--line-ptl);
+}
+
+.line-turistico {
+ border-color: var(--line-turistico);
+}
+
+.line-u1 {
+ border-color: var(--line-u1);
+}
+
+.line-u2 {
+ border-color: var(--line-u2);
+} \ No newline at end of file
diff --git a/src/frontend/src/components/LineIcon.tsx b/src/frontend/src/components/LineIcon.tsx
new file mode 100644
index 0000000..50fd1ec
--- /dev/null
+++ b/src/frontend/src/components/LineIcon.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import './LineIcon.css';
+
+interface LineIconProps {
+ line: string;
+}
+
+const LineIcon: React.FC<LineIconProps> = ({ line }) => {
+ const formattedLine = /^[a-zA-Z]/.test(line) ? line : `L${line}`;
+ return (
+ <span className={`line-icon line-${formattedLine.toLowerCase()}`}>
+ {formattedLine}
+ </span>
+ );
+};
+
+export default LineIcon; \ No newline at end of file
diff --git a/src/frontend/src/components/RegularTable.tsx b/src/frontend/src/components/RegularTable.tsx
new file mode 100644
index 0000000..211a47c
--- /dev/null
+++ b/src/frontend/src/components/RegularTable.tsx
@@ -0,0 +1,70 @@
+import { type StopDetails } from "../pages/Estimates";
+import LineIcon from "./LineIcon";
+
+interface RegularTableProps {
+ data: StopDetails;
+ dataDate: Date | null;
+}
+
+export const RegularTable: React.FC<RegularTableProps> = ({ data, dataDate }) => {
+
+ const absoluteArrivalTime = (minutes: number) => {
+ const now = new Date()
+ const arrival = new Date(now.getTime() + minutes * 60000)
+ return Intl.DateTimeFormat(navigator.language, {
+ hour: '2-digit',
+ minute: '2-digit'
+ }).format(arrival)
+ }
+
+ const formatDistance = (meters: number) => {
+ if (meters > 1024) {
+ return `${(meters / 1000).toFixed(1)} km`;
+ } else {
+ return `${meters} m`;
+ }
+ }
+
+ return <table className="table">
+ <caption>Estimaciones de llegadas a las {dataDate?.toLocaleTimeString()}</caption>
+
+ <thead>
+ <tr>
+ <th>Línea</th>
+ <th>Ruta</th>
+ <th>Llegada</th>
+ <th>Distancia</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {data.estimates
+ .sort((a, b) => a.minutes - b.minutes)
+ .map((estimate, idx) => (
+ <tr key={idx}>
+ <td><LineIcon line={estimate.line} /></td>
+ <td>{estimate.route}</td>
+ <td>
+ {estimate.minutes > 15
+ ? absoluteArrivalTime(estimate.minutes)
+ : `${estimate.minutes} min`}
+ </td>
+ <td>
+ {estimate.meters > -1
+ ? formatDistance(estimate.meters)
+ : "No disponible"
+ }
+ </td>
+ </tr>
+ ))}
+ </tbody>
+
+ {data?.estimates.length === 0 && (
+ <tfoot>
+ <tr>
+ <td colSpan={4}>No hay estimaciones disponibles</td>
+ </tr>
+ </tfoot>
+ )}
+ </table>
+}
diff --git a/src/frontend/src/components/StopItem.css b/src/frontend/src/components/StopItem.css
new file mode 100644
index 0000000..9feb2d1
--- /dev/null
+++ b/src/frontend/src/components/StopItem.css
@@ -0,0 +1,54 @@
+/* Stop Item Styling */
+
+.stop-notes {
+ font-size: 0.85rem;
+ font-style: italic;
+ color: #666;
+ margin: 2px 0;
+}
+
+.stop-amenities {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ margin-top: 4px;
+}
+
+.amenity-tag {
+ font-size: 0.75rem;
+ background-color: #e8f4f8;
+ color: #0078d4;
+ border-radius: 4px;
+ padding: 2px 6px;
+ display: inline-block;
+}
+
+/* Different colors for different amenity types */
+.amenity-tag[data-amenity="shelter"] {
+ background-color: #e3f1df;
+ color: #107c41;
+}
+
+.amenity-tag[data-amenity="bench"] {
+ background-color: #f0e8fc;
+ color: #5c2e91;
+}
+
+.amenity-tag[data-amenity="real-time display"] {
+ background-color: #fff4ce;
+ color: #986f0b;
+}
+
+/* When there are alternate names available, show an indicator */
+.has-alternate-names {
+ position: relative;
+}
+
+.has-alternate-names::after {
+ content: "⋯";
+ position: absolute;
+ right: -15px;
+ top: 0;
+ color: #0078d4;
+ font-weight: bold;
+} \ No newline at end of file
diff --git a/src/frontend/src/components/StopItem.tsx b/src/frontend/src/components/StopItem.tsx
new file mode 100644
index 0000000..29370b7
--- /dev/null
+++ b/src/frontend/src/components/StopItem.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { Link } from 'react-router';
+import StopDataProvider, { type Stop } from '../data/StopDataProvider';
+import LineIcon from './LineIcon';
+
+interface StopItemProps {
+ stop: Stop;
+}
+
+const StopItem: React.FC<StopItemProps> = ({ stop }) => {
+
+ return (
+ <li className="list-item">
+ <Link className="list-item-link" to={`/estimates/${stop.stopId}`}>
+ {stop.favourite && <span className="favourite-icon">★</span>} ({stop.stopId}) {StopDataProvider.getDisplayName(stop)}
+ <div className="line-icons">
+ {stop.lines?.map(line => <LineIcon key={line} line={line} />)}
+ </div>
+
+ </Link>
+ </li>
+ );
+};
+
+export default StopItem;
diff --git a/src/frontend/src/controls/LocateControl.ts b/src/frontend/src/controls/LocateControl.ts
new file mode 100644
index 0000000..26effa5
--- /dev/null
+++ b/src/frontend/src/controls/LocateControl.ts
@@ -0,0 +1,67 @@
+import { createControlComponent } from '@react-leaflet/core';
+import { LocateControl as LeafletLocateControl, type LocateOptions } from 'leaflet.locatecontrol';
+import "leaflet.locatecontrol/dist/L.Control.Locate.min.css";
+import { useEffect } from 'react';
+import { useMap } from 'react-leaflet';
+import { useApp } from '../AppContext';
+
+interface EnhancedLocateControlProps {
+ options?: LocateOptions;
+}
+
+// Componente que usa el contexto para manejar la localización
+export const EnhancedLocateControl = (props: EnhancedLocateControlProps) => {
+ const map = useMap();
+ const { mapState, setUserLocation, setLocationPermission } = useApp();
+
+ useEffect(() => {
+ // Configuración por defecto del control de localización
+ const defaultOptions: LocateOptions = {
+ position: 'topright',
+ strings: {
+ title: 'Mostrar mi ubicación',
+ },
+ flyTo: true,
+ onLocationError: (err) => {
+ console.error('Error en la localización:', err);
+ setLocationPermission(false);
+ },
+ returnToPrevBounds: true,
+ showPopup: false,
+ };
+
+ // Combinamos las opciones por defecto con las personalizadas
+ const options = { ...defaultOptions, ...props.options };
+
+ // Creamos la instancia del control
+ const locateControl = new LeafletLocateControl(options);
+
+ // Añadimos el control al mapa
+ locateControl.addTo(map);
+
+ // Si tenemos permiso de ubicación y ya conocemos la ubicación del usuario,
+ // podemos activarla automáticamente
+ if (mapState.hasLocationPermission && mapState.userLocation) {
+ // Esperamos a que el mapa esté listo
+ setTimeout(() => {
+ try {
+ locateControl.start();
+ } catch (e) {
+ console.error('Error al iniciar la localización automática', e);
+ }
+ }, 1000);
+ }
+
+ return () => {
+ // Limpieza al desmontar el componente
+ locateControl.remove();
+ };
+ }, [map, mapState.hasLocationPermission, mapState.userLocation, props.options, setLocationPermission, setUserLocation]);
+
+ return null;
+};
+
+// Exportamos también el control base por compatibilidad
+export const LocateControl = createControlComponent(
+ (props) => new LeafletLocateControl(props)
+);
diff --git a/src/frontend/src/data/StopDataProvider.ts b/src/frontend/src/data/StopDataProvider.ts
new file mode 100644
index 0000000..0c1e46e
--- /dev/null
+++ b/src/frontend/src/data/StopDataProvider.ts
@@ -0,0 +1,160 @@
+export interface CachedStopList {
+ timestamp: number;
+ data: Stop[];
+}
+
+export type StopName = {
+ original: string;
+ intersect?: string;
+}
+
+export interface Stop {
+ stopId: number;
+ name: StopName;
+ latitude?: number;
+ longitude?: number;
+ lines: string[];
+ favourite?: boolean;
+}
+
+// In-memory cache and lookup map
+let cachedStops: Stop[] | null = null;
+let stopsMap: Record<number, Stop> = {};
+// Custom names loaded from localStorage
+let customNames: Record<number, string> = {};
+
+// Initialize cachedStops and customNames once
+async function initStops() {
+ if (!cachedStops) {
+ const response = await fetch('/stops.json');
+ const stops = await response.json() as Stop[];
+ // build array and map
+ stopsMap = {};
+ cachedStops = stops.map(stop => {
+ const entry = { ...stop, favourite: false } as Stop;
+ stopsMap[stop.stopId] = entry;
+ return entry;
+ });
+ // load custom names
+ const rawCustom = localStorage.getItem('customStopNames');
+ if (rawCustom) customNames = JSON.parse(rawCustom) as Record<number, string>;
+ }
+}
+
+async function getStops(): Promise<Stop[]> {
+ await initStops();
+ // update favourites
+ const rawFav = localStorage.getItem('favouriteStops');
+ const favouriteStops = rawFav ? JSON.parse(rawFav) as number[] : [];
+ cachedStops!.forEach(stop => stop.favourite = favouriteStops.includes(stop.stopId));
+ return cachedStops!;
+}
+
+// New: get single stop by id
+async function getStopById(stopId: number): Promise<Stop | undefined> {
+ await initStops();
+ const stop = stopsMap[stopId];
+ if (stop) {
+ const rawFav = localStorage.getItem('favouriteStops');
+ const favouriteStops = rawFav ? JSON.parse(rawFav) as number[] : [];
+ stop.favourite = favouriteStops.includes(stopId);
+ }
+ return stop;
+}
+
+// Updated display name to include custom names
+function getDisplayName(stop: Stop): string {
+ if (customNames[stop.stopId]) return customNames[stop.stopId];
+ const nameObj = stop.name;
+ return nameObj.intersect || nameObj.original;
+}
+
+// New: set or remove custom names
+function setCustomName(stopId: number, label: string) {
+ customNames[stopId] = label;
+ localStorage.setItem('customStopNames', JSON.stringify(customNames));
+}
+
+function removeCustomName(stopId: number) {
+ delete customNames[stopId];
+ localStorage.setItem('customStopNames', JSON.stringify(customNames));
+}
+
+// New: get custom label for a stop
+function getCustomName(stopId: number): string | undefined {
+ return customNames[stopId];
+}
+
+function addFavourite(stopId: number) {
+ const rawFavouriteStops = localStorage.getItem('favouriteStops');
+ let favouriteStops: number[] = [];
+ if (rawFavouriteStops) {
+ favouriteStops = JSON.parse(rawFavouriteStops) as number[];
+ }
+
+ if (!favouriteStops.includes(stopId)) {
+ favouriteStops.push(stopId);
+ localStorage.setItem('favouriteStops', JSON.stringify(favouriteStops));
+ }
+}
+
+function removeFavourite(stopId: number) {
+ const rawFavouriteStops = localStorage.getItem('favouriteStops');
+ let favouriteStops: number[] = [];
+ if (rawFavouriteStops) {
+ favouriteStops = JSON.parse(rawFavouriteStops) as number[];
+ }
+
+ const newFavouriteStops = favouriteStops.filter(id => id !== stopId);
+ localStorage.setItem('favouriteStops', JSON.stringify(newFavouriteStops));
+}
+
+function isFavourite(stopId: number): boolean {
+ const rawFavouriteStops = localStorage.getItem('favouriteStops');
+ if (rawFavouriteStops) {
+ const favouriteStops = JSON.parse(rawFavouriteStops) as number[];
+ return favouriteStops.includes(stopId);
+ }
+ return false;
+}
+
+const RECENT_STOPS_LIMIT = 10;
+
+function pushRecent(stopId: number) {
+ const rawRecentStops = localStorage.getItem('recentStops');
+ let recentStops: Set<number> = new Set();
+ if (rawRecentStops) {
+ recentStops = new Set(JSON.parse(rawRecentStops) as number[]);
+ }
+
+ recentStops.add(stopId);
+ if (recentStops.size > RECENT_STOPS_LIMIT) {
+ const iterator = recentStops.values();
+ const val = iterator.next().value as number;
+ recentStops.delete(val);
+ }
+
+ localStorage.setItem('recentStops', JSON.stringify(Array.from(recentStops)));
+}
+
+function getRecent(): number[] {
+ const rawRecentStops = localStorage.getItem('recentStops');
+ if (rawRecentStops) {
+ return JSON.parse(rawRecentStops) as number[];
+ }
+ return [];
+}
+
+export default {
+ getStops,
+ getStopById,
+ getCustomName,
+ getDisplayName,
+ setCustomName,
+ removeCustomName,
+ addFavourite,
+ removeFavourite,
+ isFavourite,
+ pushRecent,
+ getRecent
+};
diff --git a/src/frontend/src/main.tsx b/src/frontend/src/main.tsx
new file mode 100644
index 0000000..48ff63c
--- /dev/null
+++ b/src/frontend/src/main.tsx
@@ -0,0 +1,43 @@
+import '@fontsource-variable/outfit'
+import './styles/Pages.css'
+
+import { createRoot } from 'react-dom/client'
+import { createBrowserRouter, Navigate, RouterProvider } from 'react-router'
+import { StopList } from './pages/StopList'
+import { Estimates } from './pages/Estimates'
+import { StopMap } from './pages/Map'
+import { Layout } from './Layout'
+import { Settings } from './pages/Settings'
+import { AppProvider } from './AppContext'
+import ErrorBoundary from './ErrorBoundary'
+
+const router = createBrowserRouter([
+ {
+ path: '/',
+ element: <Layout><Navigate to="/stops" /></Layout>,
+ },
+ {
+ path: '/stops',
+ element: <Layout><StopList /></Layout>,
+ },
+ {
+ path: '/map',
+ element: <Layout><StopMap /></Layout>,
+ },
+ {
+ path: '/estimates/:stopId',
+ element: <Layout><Estimates /></Layout>
+ },
+ {
+ path: '/settings',
+ element: <Layout><Settings /></Layout>
+ }
+])
+
+createRoot(document.getElementById('root')!).render(
+ <ErrorBoundary>
+ <AppProvider>
+ <RouterProvider router={router} />
+ </AppProvider>
+ </ErrorBoundary>
+)
diff --git a/src/frontend/src/pages/Estimates.tsx b/src/frontend/src/pages/Estimates.tsx
new file mode 100644
index 0000000..6a98731
--- /dev/null
+++ b/src/frontend/src/pages/Estimates.tsx
@@ -0,0 +1,99 @@
+import { type JSX, useEffect, useState } from "react";
+import { useParams } from "react-router";
+import StopDataProvider from "../data/StopDataProvider";
+import { Star, Edit2 } from 'lucide-react';
+import "../styles/Estimates.css";
+import { RegularTable } from "../components/RegularTable";
+import { useApp } from "../AppContext";
+import { GroupedTable } from "../components/GroupedTable";
+
+export interface StopDetails {
+ stop: {
+ id: number;
+ name: string;
+ latitude: number;
+ longitude: number;
+ }
+ estimates: {
+ line: string;
+ route: string;
+ minutes: number;
+ meters: number;
+ }[]
+}
+
+const loadData = async (stopId: string) => {
+ const resp = await fetch(`/api/GetStopEstimates?id=${stopId}`);
+ return await resp.json();
+};
+
+export function Estimates() {
+ const params = useParams();
+ const stopIdNum = parseInt(params.stopId ?? "");
+ const [customName, setCustomName] = useState<string | undefined>(undefined);
+ const [data, setData] = useState<StopDetails | null>(null);
+ const [dataDate, setDataDate] = useState<Date | null>(null);
+ const [favourited, setFavourited] = useState(false);
+ const { tableStyle } = useApp();
+
+ useEffect(() => {
+ loadData(params.stopId!)
+ .then((body: StopDetails) => {
+ setData(body);
+ setDataDate(new Date());
+ setCustomName(StopDataProvider.getCustomName(stopIdNum));
+ })
+
+
+ StopDataProvider.pushRecent(parseInt(params.stopId ?? ""));
+
+ setFavourited(
+ StopDataProvider.isFavourite(parseInt(params.stopId ?? ""))
+ );
+ }, [params.stopId]);
+
+
+ const toggleFavourite = () => {
+ if (favourited) {
+ StopDataProvider.removeFavourite(stopIdNum);
+ setFavourited(false);
+ } else {
+ StopDataProvider.addFavourite(stopIdNum);
+ setFavourited(true);
+ }
+ }
+
+ const handleRename = () => {
+ const current = customName ?? data?.stop.name;
+ const input = window.prompt('Custom name for this stop:', current);
+ if (input === null) return; // cancelled
+ const trimmed = input.trim();
+ if (trimmed === '') {
+ StopDataProvider.removeCustomName(stopIdNum);
+ setCustomName(undefined);
+ } else {
+ StopDataProvider.setCustomName(stopIdNum, trimmed);
+ setCustomName(trimmed);
+ }
+ };
+
+ if (data === null) return <h1 className="page-title">Cargando datos en tiempo real...</h1>
+
+ return (
+ <div className="page-container">
+ <div className="estimates-header">
+ <h1 className="page-title">
+ <Star className={`star-icon ${favourited ? 'active' : ''}`} onClick={toggleFavourite} />
+ <Edit2 className="edit-icon" onClick={handleRename} />
+ {(customName ?? data.stop.name)} <span className="estimates-stop-id">({data.stop.id})</span>
+ </h1>
+ </div>
+
+ <div className="table-responsive">
+ {tableStyle === 'grouped' ?
+ <GroupedTable data={data} dataDate={dataDate} /> :
+ <RegularTable data={data} dataDate={dataDate} />}
+ </div>
+ </div>
+ )
+}
diff --git a/src/frontend/src/pages/Map.tsx b/src/frontend/src/pages/Map.tsx
new file mode 100644
index 0000000..52c73f8
--- /dev/null
+++ b/src/frontend/src/pages/Map.tsx
@@ -0,0 +1,75 @@
+import StopDataProvider, { type Stop } from "../data/StopDataProvider";
+
+import 'leaflet/dist/leaflet.css'
+import 'react-leaflet-markercluster/styles'
+
+import { useEffect, useState } from 'react';
+import LineIcon from '../components/LineIcon';
+import { Link } from 'react-router';
+import { MapContainer, TileLayer, Marker, Popup, useMapEvents } from "react-leaflet";
+import MarkerClusterGroup from "react-leaflet-markercluster";
+import { Icon, type LatLngTuple } from "leaflet";
+import { EnhancedLocateControl } from "../controls/LocateControl";
+import { useApp } from "../AppContext";
+
+const icon = new Icon({
+ iconUrl: '/map-pin-icon.png',
+ iconSize: [25, 41],
+ iconAnchor: [12, 41],
+ popupAnchor: [1, -34],
+ shadowSize: [41, 41]
+});
+
+// Componente auxiliar para detectar cambios en el mapa
+const MapEventHandler = () => {
+ const { updateMapState } = useApp();
+
+ const map = useMapEvents({
+ moveend: () => {
+ const center = map.getCenter();
+ const zoom = map.getZoom();
+ updateMapState([center.lat, center.lng], zoom);
+ }
+ });
+
+ return null;
+};
+
+// Componente principal del mapa
+export function StopMap() {
+ const [stops, setStops] = useState<Stop[]>([]);
+ const { mapState } = useApp();
+
+ useEffect(() => {
+ StopDataProvider.getStops().then(setStops);
+ }, []);
+
+ return (
+ <MapContainer
+ center={mapState.center}
+ zoom={mapState.zoom}
+ scrollWheelZoom={true}
+ style={{ height: '100%' }}
+ >
+ <TileLayer
+ attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a>, &copy; <a href="https://carto.com/attributions">CARTO</a>'
+ url="https://d.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png"
+ />
+ <EnhancedLocateControl />
+ <MapEventHandler />
+ <MarkerClusterGroup>
+ {stops.map(stop => (
+ <Marker key={stop.stopId} position={[stop.latitude, stop.longitude] as LatLngTuple} icon={icon}>
+ <Popup>
+ <Link to={`/estimates/${stop.stopId}`}>{StopDataProvider.getDisplayName(stop)}</Link>
+ <br />
+ {stop.lines.map((line) => (
+ <LineIcon key={line} line={line} />
+ ))}
+ </Popup>
+ </Marker>
+ ))}
+ </MarkerClusterGroup>
+ </MapContainer>
+ );
+}
diff --git a/src/frontend/src/pages/Settings.tsx b/src/frontend/src/pages/Settings.tsx
new file mode 100644
index 0000000..1ad15ab
--- /dev/null
+++ b/src/frontend/src/pages/Settings.tsx
@@ -0,0 +1,65 @@
+import { useApp } from "../AppContext";
+import "../styles/Settings.css";
+
+export function Settings() {
+ const { theme, setTheme, tableStyle, setTableStyle, mapPositionMode, setMapPositionMode } = useApp();
+
+ return (
+ <div className="about-page">
+ <h1 className="page-title">Sobre UrbanoVigo Web</h1>
+ <p className="about-description">
+ Aplicación web para encontrar paradas y tiempos de llegada de los autobuses
+ urbanos de Vigo, España.
+ </p>
+ <section className="settings-section">
+ <h2>Ajustes</h2>
+ <div className="settings-content-inline">
+ <label htmlFor="theme" className="form-label-inline">Modo:</label>
+ <select id="theme" className="form-select-inline" value={theme} onChange={(e) => setTheme(e.target.value as "light" | "dark")}>
+ <option value="light">Claro</option>
+ <option value="dark">Oscuro</option>
+ </select>
+ </div>
+ <div className="settings-content-inline">
+ <label htmlFor="tableStyle" className="form-label-inline">Estilo de tabla:</label>
+ <select id="tableStyle" className="form-select-inline" value={tableStyle} onChange={(e) => setTableStyle(e.target.value as "regular" | "grouped")}>
+ <option value="regular">Mostrar por orden</option>
+ <option value="grouped">Agrupar por línea</option>
+ </select>
+ </div>
+ <div className="settings-content-inline">
+ <label htmlFor="mapPositionMode" className="form-label-inline">Posición del mapa:</label>
+ <select id="mapPositionMode" className="form-select-inline" value={mapPositionMode} onChange={e => setMapPositionMode(e.target.value as 'gps' | 'last')}>
+ <option value="gps">Posición GPS</option>
+ <option value="last">Donde lo dejé</option>
+ </select>
+ </div>
+ <details className="form-details">
+ <summary>¿Qué significa esto?</summary>
+ <p>
+ La tabla de horarios puede mostrarse de dos formas:
+ </p>
+ <dl>
+ <dt>Mostrar por orden</dt>
+ <dd>Las paradas se muestran en el orden en que se visitan. Aplicaciones como Infobus (Vitrasa) usan este estilo.</dd>
+ <dt>Agrupar por línea</dt>
+ <dd>Las paradas se agrupan por la línea de autobús. Aplicaciones como iTranvias (A Coruña) o Moovit (más o menos) usan este estilo.</dd>
+ </dl>
+ </details>
+ </section>
+ <h2>Créditos</h2>
+ <p>
+ <a href="https://github.com/arielcostas/urbanovigo-web" className="about-link" rel="nofollow noreferrer noopener">
+ Código en GitHub
+ </a> -
+ Desarrollado por <a href="https://www.costas.dev" className="about-link" rel="nofollow noreferrer noopener">
+ Ariel Costas
+ </a>
+ </p>
+ <p>
+ Datos obtenidos de <a href="https://datos.vigo.org" className="about-link" rel="nofollow noreferrer noopener">datos.vigo.org</a> bajo
+ licencia <a href="https://opendefinition.org/licenses/odc-by/" className="about-link" rel="nofollow noreferrer noopener">Open Data Commons Attribution License</a>
+ </p>
+ </div>
+ )
+} \ No newline at end of file
diff --git a/src/frontend/src/pages/StopList.tsx b/src/frontend/src/pages/StopList.tsx
new file mode 100644
index 0000000..59a1942
--- /dev/null
+++ b/src/frontend/src/pages/StopList.tsx
@@ -0,0 +1,135 @@
+import { useEffect, useMemo, useRef, useState } from "react";
+import StopDataProvider, { type Stop } from "../data/StopDataProvider";
+import StopItem from "../components/StopItem";
+import Fuse from "fuse.js";
+
+const placeholders = [
+ "Urzaiz",
+ "Gran Vía",
+ "Castelao",
+ "García Barbón",
+ "Valladares",
+ "Florida",
+ "Pizarro",
+ "Estrada Madrid",
+ "Sanjurjo Badía"
+];
+
+export function StopList() {
+ const [data, setData] = useState<Stop[] | null>(null)
+ const [searchResults, setSearchResults] = useState<Stop[] | null>(null);
+ const searchTimeout = useRef<NodeJS.Timeout | null>(null);
+
+ const randomPlaceholder = useMemo(() => placeholders[Math.floor(Math.random() * placeholders.length)], []);
+ const fuse = useMemo(() => new Fuse(data || [], { threshold: 0.3, keys: ['name.original'] }), [data]);
+
+ useEffect(() => {
+ StopDataProvider.getStops().then((stops: Stop[]) => setData(stops))
+ }, []);
+
+ const handleStopSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
+ const stopName = event.target.value || "";
+
+ if (searchTimeout.current) {
+ clearTimeout(searchTimeout.current);
+ }
+
+ searchTimeout.current = setTimeout(() => {
+ if (stopName.length === 0) {
+ setSearchResults(null);
+ return;
+ }
+
+ if (!data) {
+ console.error("No data available for search");
+ return;
+ }
+
+ const results = fuse.search(stopName);
+ const items = results.map(result => result.item);
+ setSearchResults(items);
+ }, 300);
+ }
+
+ const favouritedStops = useMemo(() => {
+ return data?.filter(stop => stop.favourite) ?? []
+ }, [data])
+
+ const recentStops = useMemo(() => {
+ // no recent items if data not loaded
+ if (!data) return null;
+ const recentIds = StopDataProvider.getRecent();
+ if (recentIds.length === 0) return null;
+ // map and filter out missing entries
+ const stopsList = recentIds
+ .map(id => data.find(stop => stop.stopId === id))
+ .filter((s): s is Stop => Boolean(s));
+ return stopsList.reverse();
+ }, [data]);
+
+ if (data === null) return <h1 className="page-title">Loading...</h1>
+
+ return (
+ <div className="page-container">
+ <h1 className="page-title">UrbanoVigo Web</h1>
+
+ <form className="search-form">
+ <div className="form-group">
+ <label className="form-label" htmlFor="stopName">
+ Buscar paradas
+ </label>
+ <input className="form-input" type="text" placeholder={randomPlaceholder} id="stopName" onChange={handleStopSearch} />
+ </div>
+ </form>
+
+ {searchResults && searchResults.length > 0 && (
+ <div className="list-container">
+ <h2 className="page-subtitle">Resultados de la búsqueda</h2>
+ <ul className="list">
+ {searchResults.map((stop: Stop) => (
+ <StopItem key={stop.stopId} stop={stop} />
+ ))}
+ </ul>
+ </div>
+ )}
+
+ <div className="list-container">
+ <h2 className="page-subtitle">Paradas favoritas</h2>
+
+ {favouritedStops?.length === 0 && (
+ <p className="message">
+ Accede a una parada y márcala como favorita para verla aquí.
+ </p>
+ )}
+
+ <ul className="list">
+ {favouritedStops?.sort((a, b) => a.stopId - b.stopId).map((stop: Stop) => (
+ <StopItem key={stop.stopId} stop={stop} />
+ ))}
+ </ul>
+ </div>
+
+ {recentStops && recentStops.length > 0 && (
+ <div className="list-container">
+ <h2 className="page-subtitle">Recientes</h2>
+
+ <ul className="list">
+ {recentStops.map((stop: Stop) => (
+ <StopItem key={stop.stopId} stop={stop} />
+ ))}
+ </ul>
+ </div>
+ )}
+
+ <div className="list-container">
+ <h2 className="page-subtitle">Paradas</h2>
+
+ <ul className="list">
+ {data?.sort((a, b) => a.stopId - b.stopId).map((stop: Stop) => (
+ <StopItem key={stop.stopId} stop={stop} />
+ ))}
+ </ul>
+ </div>
+ </div>
+ )
+}
diff --git a/src/frontend/src/styles/Estimates.css b/src/frontend/src/styles/Estimates.css
new file mode 100644
index 0000000..86ca09b
--- /dev/null
+++ b/src/frontend/src/styles/Estimates.css
@@ -0,0 +1,105 @@
+.table-responsive {
+ overflow-x: auto;
+ margin-bottom: 1.5rem;
+}
+
+.table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.table caption {
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+}
+
+.table th,
+.table td {
+ padding: 0.75rem;
+ text-align: left;
+ border-bottom: 1px solid #eee;
+}
+
+.table th {
+ border-bottom: 2px solid #ddd;
+}
+
+.table tfoot td {
+ text-align: center;
+}
+
+/* Estimates page specific styles */
+.estimates-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 1rem;
+}
+
+.estimates-stop-id {
+ font-size: 1rem;
+ color: var(--subtitle-color);
+ margin-left: 0.5rem;
+}
+
+.estimates-arrival {
+ color: #28a745;
+ font-weight: 500;
+}
+
+.estimates-delayed {
+ color: #dc3545;
+}
+
+.button-group {
+ display: flex;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+ flex-wrap: wrap;
+}
+
+.button {
+ padding: 0.75rem 1rem;
+ background-color: var(--button-background-color);
+ color: white;
+ border: none;
+ border-radius: 8px;
+ font-size: 1rem;
+ font-weight: 500;
+ cursor: pointer;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+}
+
+.button:hover {
+ background-color: var(--button-hover-background-color);
+}
+
+.button:disabled {
+ background-color: var(--button-disabled-background-color);
+ cursor: not-allowed;
+}
+
+.star-icon {
+ margin-right: 0.5rem;
+ color: #ccc;
+ fill: none;
+}
+
+.star-icon.active {
+ color: var(--star-color);
+ /* Yellow color for active star */
+ fill: var(--star-color);
+}
+
+/* Pencil (edit) icon next to header */
+.edit-icon {
+ margin-right: 0.5rem;
+ color: #ccc;
+ cursor: pointer;
+ stroke-width: 2px;
+}
+
+.edit-icon:hover {
+ color: var(--star-color);
+} \ No newline at end of file
diff --git a/src/frontend/src/styles/Map.css b/src/frontend/src/styles/Map.css
new file mode 100644
index 0000000..3af112a
--- /dev/null
+++ b/src/frontend/src/styles/Map.css
@@ -0,0 +1,86 @@
+/* Map page specific styles */
+.map-container {
+ height: calc(100vh - 140px);
+ margin: -16px;
+ margin-bottom: 1rem;
+ position: relative;
+}
+
+/* Fullscreen map styles */
+.fullscreen-container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ padding: 0;
+ margin: 0;
+ max-width: none;
+ overflow: hidden;
+}
+
+.fullscreen-map {
+ width: 100%;
+ height: 100%;
+}
+
+.fullscreen-loading {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ width: 100vw;
+ font-size: 1.8rem;
+ font-weight: 600;
+ color: var(--text-color);
+}
+
+/* Map marker and popup styles */
+.stop-marker {
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
+ transition: all 0.2s ease-in-out;
+}
+
+.stop-marker:hover {
+ transform: scale(1.2);
+}
+
+.maplibregl-popup {
+ max-width: 250px;
+}
+
+.maplibregl-popup-content {
+ padding: 12px;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+}
+
+.popup-line-icons {
+ display: flex;
+ flex-wrap: wrap;
+ margin: 6px 0;
+ gap: 5px;
+}
+
+.popup-line {
+ display: inline-block;
+ background-color: var(--button-background-color);
+ color: white;
+ padding: 2px 6px;
+ margin-right: 4px;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ font-weight: 500;
+}
+
+.popup-link {
+ display: block;
+ margin-top: 8px;
+ color: var(--button-background-color);
+ text-decoration: none;
+ font-weight: 500;
+}
+
+.popup-link:hover {
+ text-decoration: underline;
+} \ No newline at end of file
diff --git a/src/frontend/src/styles/Pages.css b/src/frontend/src/styles/Pages.css
new file mode 100644
index 0000000..90ffad2
--- /dev/null
+++ b/src/frontend/src/styles/Pages.css
@@ -0,0 +1,364 @@
+:root {
+ --background-color: #ffffff;
+ --text-color: #333333;
+ --subtitle-color: #444444;
+ --border-color: #eeeeee;
+ --button-background-color: #007bff;
+ --button-hover-background-color: #0069d9;
+ --button-disabled-background-color: #cccccc;
+ --star-color: #ffcc00;
+ --message-background-color: #f8f9fa;
+
+ font-family: 'Outfit Variable', Roboto, Arial, sans-serif;
+}
+
+[data-theme='dark'] {
+ --background-color: #121212;
+ --text-color: #ffffff;
+ --subtitle-color: #bbbbbb;
+ --border-color: #444444;
+ --button-background-color: #1e88e5;
+ --button-hover-background-color: #1565c0;
+ --button-disabled-background-color: #555555;
+ --star-color: #ffcc00;
+ --message-background-color: #333333;
+}
+
+body {
+ background-color: var(--background-color);
+ color: var(--text-color);
+}
+
+/* Mobile-first page styles */
+
+/* Common page styles */
+.page-container {
+ max-width: 100%;
+ padding: 0 16px;
+ background-color: var(--background-color);
+ color: var(--text-color);
+}
+
+.page-title {
+ font-size: 1.8rem;
+ margin-bottom: 1rem;
+ font-weight: 600;
+ color: var(--text-color);
+}
+
+.page-subtitle {
+ font-size: 1.4rem;
+ margin-top: 1.5rem;
+ margin-bottom: 0.75rem;
+ font-weight: 500;
+ color: var(--subtitle-color);
+}
+
+/* Form styles */
+.search-form {
+ margin-bottom: 1.5rem;
+}
+
+.form-group {
+ margin-bottom: 1rem;
+ display: flex;
+ flex-direction: column;
+}
+
+.form-label {
+ font-size: 0.9rem;
+ margin-bottom: 0.25rem;
+ font-weight: 500;
+}
+
+.form-input {
+ padding: 0.75rem;
+ font-size: 1rem;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+}
+
+.form-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 1rem;
+
+ padding: 0.75rem 1rem;
+ background-color: var(--button-background-color);
+ color: white;
+ border: none;
+ border-radius: 8px;
+ font-size: 1rem;
+ font-weight: 500;
+ cursor: pointer;
+ width: 100%;
+ margin-top: 0.5rem;
+}
+
+.form-button:hover {
+ background-color: var(--button-hover-background-color);
+}
+
+/* List styles */
+.list-container {
+ margin-bottom: 1.5rem;
+}
+
+.list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.list-item {
+ padding: 1rem;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.list-item-link {
+ display: block;
+ color: var(--text-color);
+ text-decoration: none;
+ font-size: 1.1rem; /* Increased font size for stop name */
+}
+
+.list-item-link:hover {
+ color: var(--button-background-color);
+}
+
+.list-item-link:hover .line-icon {
+ color: var(--text-color);
+}
+
+.distance-info {
+ font-size: 0.9rem;
+ color: var(--subtitle-color);
+}
+
+/* Message styles */
+.message {
+ padding: 1rem;
+ background-color: var(--message-background-color);
+ border-radius: 8px;
+ margin-bottom: 1rem;
+}
+
+/* About page specific styles */
+.about-page {
+ text-align: center;
+ padding: 1rem;
+}
+
+.about-version {
+ color: var(--subtitle-color);
+ font-size: 0.9rem;
+ margin-top: 2rem;
+}
+
+.about-description {
+ margin-top: 1rem;
+ line-height: 1.6;
+}
+
+/* Map page specific styles */
+.map-container {
+ height: calc(100vh - 140px);
+ margin: -16px;
+ margin-bottom: 1rem;
+ position: relative;
+}
+
+/* Fullscreen map styles */
+.fullscreen-container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ padding: 0;
+ margin: 0;
+ max-width: none;
+ overflow: hidden;
+}
+
+.fullscreen-map {
+ width: 100%;
+ height: 100%;
+}
+
+.fullscreen-loading {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ width: 100vw;
+ font-size: 1.8rem;
+ font-weight: 600;
+ color: var(--text-color);
+}
+
+/* Map marker and popup styles */
+.stop-marker {
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
+ transition: all 0.2s ease-in-out;
+}
+
+.stop-marker:hover {
+ transform: scale(1.2);
+}
+
+.maplibregl-popup {
+ max-width: 250px;
+}
+
+.maplibregl-popup-content {
+ padding: 12px;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+}
+
+.popup-line-icons {
+ display: flex;
+ flex-wrap: wrap;
+ margin: 6px 0;
+ gap: 5px;
+}
+
+.popup-line {
+ display: inline-block;
+ background-color: var(--button-background-color);
+ color: white;
+ padding: 2px 6px;
+ margin-right: 4px;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ font-weight: 500;
+}
+
+.popup-link {
+ display: block;
+ margin-top: 8px;
+ color: var(--button-background-color);
+ text-decoration: none;
+ font-weight: 500;
+}
+
+.popup-link:hover {
+ text-decoration: underline;
+}
+
+/* Estimates page specific styles */
+.estimates-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 1rem;
+}
+
+.estimates-stop-id {
+ font-size: 1rem;
+ color: var(--subtitle-color);
+ margin-left: 0.5rem;
+}
+
+.estimates-arrival {
+ color: #28a745;
+ font-weight: 500;
+}
+
+.estimates-delayed {
+ color: #dc3545;
+}
+
+.button-group {
+ display: flex;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+ flex-wrap: wrap;
+}
+
+.button {
+ padding: 0.75rem 1rem;
+ background-color: var(--button-background-color);
+ color: white;
+ border: none;
+ border-radius: 8px;
+ font-size: 1rem;
+ font-weight: 500;
+ cursor: pointer;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+}
+
+.button:hover {
+ background-color: var(--button-hover-background-color);
+}
+
+.button:disabled {
+ background-color: var(--button-disabled-background-color);
+ cursor: not-allowed;
+}
+
+.star-icon {
+ margin-right: 0.5rem;
+ color: #ccc;
+ fill: none;
+}
+
+.star-icon.active {
+ color: var(--star-color); /* Yellow color for active star */
+ fill: var(--star-color);
+}
+
+/* Tablet and larger breakpoint */
+@media (min-width: 768px) {
+ .page-container {
+ width: 90%;
+ max-width: 768px;
+ margin: 0 auto;
+ }
+
+ .page-title {
+ font-size: 2.2rem;
+ }
+
+ .search-form {
+ display: flex;
+ align-items: flex-end;
+ gap: 1rem;
+ }
+
+ .form-group {
+ flex: 1;
+ margin-bottom: 0;
+ }
+
+ .form-button {
+ width: auto;
+ margin-top: 0;
+ }
+
+ .list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 1rem;
+ }
+
+ .list-item {
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ margin-bottom: 0;
+ }
+}
+
+/* Desktop breakpoint */
+@media (min-width: 1024px) {
+ .page-container {
+ max-width: 1024px;
+ }
+
+ .list {
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+ }
+} \ No newline at end of file
diff --git a/src/frontend/src/styles/Settings.css b/src/frontend/src/styles/Settings.css
new file mode 100644
index 0000000..934577d
--- /dev/null
+++ b/src/frontend/src/styles/Settings.css
@@ -0,0 +1,94 @@
+/* About page specific styles */
+.about-page {
+ text-align: center;
+ padding: 1rem;
+}
+
+.about-version {
+ color: var(--subtitle-color);
+ font-size: 0.9rem;
+ margin-top: 2rem;
+}
+
+.about-description {
+ margin-top: 1rem;
+ line-height: 1.6;
+}
+
+.settings-section {
+ margin-bottom: 2em;
+ padding: 1rem;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ background-color: var(--message-background-color);
+ text-align: left;
+}
+
+.settings-section h2 {
+ margin-bottom: 1em;
+}
+
+.settings-content {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ margin-bottom: 1em;
+}
+
+.settings-content-inline {
+ display: flex;
+ align-items: center;
+ margin-bottom: 1em;
+}
+
+.settings-section .form-button {
+ margin-bottom: 1em;
+ padding: 0.75rem 1.5rem;
+ font-size: 1.1rem;
+}
+
+.settings-section .form-select-inline {
+ margin-left: 0.5em;
+ padding: 0.5rem;
+ font-size: 1rem;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+}
+
+.settings-section .form-label-inline {
+ font-weight: 500;
+}
+
+.settings-section .form-label {
+ display: block;
+ margin-bottom: 0.5em;
+ font-weight: 500;
+}
+
+.settings-section .form-description {
+ margin-top: 0.5em;
+ font-size: 0.9rem;
+ color: var(--subtitle-color);
+}
+
+.settings-section .form-details {
+ margin-top: 0.5em;
+ font-size: 0.9rem;
+ color: var(--subtitle-color);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 0.5rem;
+}
+
+.settings-section .form-details summary {
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.settings-section .form-details p {
+ margin-top: 0.5em;
+}
+
+.settings-section p {
+ margin-top: 0.5em;
+} \ No newline at end of file
diff --git a/src/frontend/src/vite-env.d.ts b/src/frontend/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/src/frontend/src/vite-env.d.ts
@@ -0,0 +1 @@
+/// <reference types="vite/client" />
diff --git a/src/frontend/tsconfig.json b/src/frontend/tsconfig.json
new file mode 100644
index 0000000..dc391a4
--- /dev/null
+++ b/src/frontend/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "include": [
+ "**/*",
+ "**/.server/**/*",
+ "**/.client/**/*",
+ ".react-router/types/**/*"
+ ],
+ "compilerOptions": {
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
+ "types": ["node", "vite/client"],
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "rootDirs": [".", "./.react-router/types"],
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./app/*"]
+ },
+ "esModuleInterop": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "strict": true
+ }
+}
diff --git a/src/frontend/vite.config.ts b/src/frontend/vite.config.ts
new file mode 100644
index 0000000..e7b5a95
--- /dev/null
+++ b/src/frontend/vite.config.ts
@@ -0,0 +1,26 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react-swc'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ proxy: {
+ '^/api': {
+ target: 'https://localhost:7240',
+ secure: false
+ }
+ }
+ },
+ build: {
+ rollupOptions: {
+ output: {
+ manualChunks: {
+ react: ['react', 'react-dom'],
+ router: ['react-router'],
+ leaflet: ['leaflet', 'react-leaflet', 'leaflet.locatecontrol', 'leaflet.markercluster']
+ }
+ }
+ }
+ }
+})