@dacostafilipe/react-geoportail 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +272 -0
  2. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,272 @@
1
+ # react-geoportail
2
+
3
+ > **Disclaimer:** This is an **unofficial, community-built** React package. It is not affiliated with, endorsed by, or supported by the [Geoportail Luxembourg](https://www.geoportail.lu) team or the Administration du Cadastre et de la Topographie (ACT). For the official API, see [apiv3.geoportail.lu](https://apiv3.geoportail.lu/proj/1.0/build/apidoc/).
4
+
5
+ An unofficial React SDK for the [Geoportail Luxembourg v3 API](https://apiv3.geoportail.lu/proj/1.0/build/apidoc/). Provides a map component and hooks for geocoding, all typed with TypeScript.
6
+
7
+ ## Features
8
+
9
+ - **`<GeoportailMap>`** — render a Geoportail map with optional pin/marker support
10
+ - **`useReverseGeocode`** — look up a Luxembourg address from lat/lon coordinates
11
+ - **`useGeocode`** — search for coordinates from an address string
12
+ - Coordinate conversion utilities (EPSG:2169 ↔ WGS84) included
13
+ - Zero runtime npm dependencies — uses the official `apiv3loader.js` script
14
+
15
+ ## Requirements
16
+
17
+ - React 17 or later
18
+ - A modern browser (the Geoportail API requires it)
19
+ - For **production deployments**: register your domain with ACT at `support.geoportail@act.etat.lu`. The API works without restriction on `localhost`.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install @dacostafilipe/react-geoportail
25
+ ```
26
+
27
+ The Geoportail API script (`apiv3.geoportail.lu/apiv3loader.js`) is injected automatically — no manual `<script>` tag needed.
28
+
29
+ ## Quick start
30
+
31
+ ```tsx
32
+ import { GeoportailMap } from '@dacostafilipe/react-geoportail';
33
+
34
+ export default function App() {
35
+ return (
36
+ <GeoportailMap
37
+ center={{ lat: 49.6116, lon: 6.1319 }}
38
+ zoom={13}
39
+ style={{ height: 500 }}
40
+ />
41
+ );
42
+ }
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Components
48
+
49
+ ### `<GeoportailMap>`
50
+
51
+ Renders a Geoportail map inside a `div`. The map fills its container — set a `height` via `style` or `className`.
52
+
53
+ ```tsx
54
+ <GeoportailMap
55
+ center={{ lat: 49.6116, lon: 6.1319 }}
56
+ zoom={13}
57
+ bgLayer="basemap_2015_global"
58
+ markerMode="click"
59
+ onMarkerPlace={(coords) => console.log(coords.lat, coords.lon)}
60
+ style={{ height: 480 }}
61
+ />
62
+ ```
63
+
64
+ #### Props
65
+
66
+ | Prop | Type | Default | Description |
67
+ |------|------|---------|-------------|
68
+ | `center` | `LatLon` | Luxembourg City | Initial map center |
69
+ | `zoom` | `number` | `12` | Initial zoom level (1–20) |
70
+ | `bgLayer` | `string` | `'basemap_2015_global'` | Background layer identifier |
71
+ | `markerMode` | `'none' \| 'fixed' \| 'click'` | `'none'` | Controls pin behaviour (see below) |
72
+ | `markerPosition` | `LatLon` | — | Pin position for `'fixed'` mode, or initial pin for `'click'` mode |
73
+ | `onMarkerPlace` | `(coords: LatLon) => void` | — | Called when the user places a pin (`markerMode='click'`) |
74
+ | `layers` | `number[]` | — | Additional Geoportail layer IDs to overlay |
75
+ | `className` | `string` | — | CSS class for the container `div` |
76
+ | `style` | `React.CSSProperties` | — | Inline styles for the container `div` |
77
+
78
+ #### Marker modes
79
+
80
+ | Mode | Behaviour |
81
+ |------|-----------|
82
+ | `'none'` | No marker shown |
83
+ | `'fixed'` | A static pin is shown at `markerPosition` |
84
+ | `'click'` | User clicks the map to place/move the pin; fires `onMarkerPlace` with the clicked WGS84 coordinates |
85
+
86
+ #### Imperative ref
87
+
88
+ Pass a `ref` typed as `GeoportailMapHandle` to control the map programmatically:
89
+
90
+ ```tsx
91
+ import { useRef } from 'react';
92
+ import { GeoportailMap, GeoportailMapHandle } from '@dacostafilipe/react-geoportail';
93
+
94
+ const mapRef = useRef<GeoportailMapHandle>(null);
95
+
96
+ <GeoportailMap ref={mapRef} ... />
97
+
98
+ // Move the map
99
+ mapRef.current?.setCenter({ lat: 49.5, lon: 6.1 });
100
+ mapRef.current?.setZoom(15);
101
+
102
+ // Access the raw lux.Map instance
103
+ const luxMap = mapRef.current?.getLuxMap();
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Hooks
109
+
110
+ ### `useReverseGeocode`
111
+
112
+ Convert a WGS84 lat/lon position to a Luxembourg address. Uses the Geoportail REST reverse geocode endpoint — no API key required.
113
+
114
+ ```tsx
115
+ import { useReverseGeocode } from '@dacostafilipe/react-geoportail';
116
+
117
+ function LocationInfo({ lat, lon }: { lat: number; lon: number }) {
118
+ const { state, lookup } = useReverseGeocode();
119
+
120
+ useEffect(() => {
121
+ lookup({ lat, lon });
122
+ }, [lat, lon]);
123
+
124
+ if (state.status === 'loading') return <p>Looking up address…</p>;
125
+ if (state.status === 'error') return <p>Error: {state.error.message}</p>;
126
+ if (state.status === 'success') return <p>{state.address.label}</p>;
127
+ return null;
128
+ }
129
+ ```
130
+
131
+ #### Return value
132
+
133
+ ```ts
134
+ const { state, lookup, reset } = useReverseGeocode();
135
+ ```
136
+
137
+ | | Type | Description |
138
+ |-|------|-------------|
139
+ | `state.status` | `'idle' \| 'loading' \| 'success' \| 'error'` | Current state |
140
+ | `state.address` | `Address \| null` | Result when `status === 'success'` |
141
+ | `state.error` | `Error \| null` | Error when `status === 'error'` |
142
+ | `lookup(pos)` | `(pos: LatLon) => void` | Trigger a lookup; cancels any in-flight request |
143
+ | `reset()` | `() => void` | Return to `'idle'` and cancel any in-flight request |
144
+
145
+ #### `Address` shape
146
+
147
+ ```ts
148
+ interface Address {
149
+ label: string; // Full formatted address
150
+ distance: number; // Distance from queried point (metres)
151
+ easting: number; // EPSG:2169 easting
152
+ northing: number; // EPSG:2169 northing
153
+ }
154
+ ```
155
+
156
+ ---
157
+
158
+ ### `useGeocode`
159
+
160
+ Search for a Luxembourg address and get back coordinates. Supports a free-text query string or structured fields.
161
+
162
+ ```tsx
163
+ import { useGeocode } from '@dacostafilipe/react-geoportail';
164
+
165
+ function AddressSearch() {
166
+ const { state, search } = useGeocode();
167
+
168
+ return (
169
+ <>
170
+ <button onClick={() => search({ queryString: 'Place d\'Armes, Luxembourg' })}>
171
+ Search
172
+ </button>
173
+
174
+ {state.status === 'success' && state.results.map((r, i) => (
175
+ <p key={i}>
176
+ {r.street} {r.num}, {r.zip} {r.locality}
177
+ — {r.latLon.lat.toFixed(5)}, {r.latLon.lon.toFixed(5)}
178
+ </p>
179
+ ))}
180
+ </>
181
+ );
182
+ }
183
+ ```
184
+
185
+ #### Return value
186
+
187
+ ```ts
188
+ const { state, search, reset } = useGeocode();
189
+ ```
190
+
191
+ | | Type | Description |
192
+ |-|------|-------------|
193
+ | `state.status` | `'idle' \| 'loading' \| 'success' \| 'error'` | Current state |
194
+ | `state.results` | `GeocodeResultItem[] \| null` | Results when `status === 'success'` |
195
+ | `state.error` | `Error \| null` | Error when `status === 'error'` |
196
+ | `search(query)` | `(query: GeocodeQuery) => void` | Trigger a search; cancels any in-flight request |
197
+ | `reset()` | `() => void` | Return to `'idle'` |
198
+
199
+ #### `GeocodeQuery` options
200
+
201
+ ```ts
202
+ // Free-text (recommended for most cases)
203
+ search({ queryString: '1 rue du Fort Thüngen, Luxembourg' });
204
+
205
+ // Structured fields
206
+ search({ num: '1', street: 'rue du Fort Thüngen', zip: '1499', locality: 'Luxembourg' });
207
+ ```
208
+
209
+ #### `GeocodeResultItem` shape
210
+
211
+ ```ts
212
+ interface GeocodeResultItem {
213
+ latLon: LatLon; // WGS84 { lat, lon }
214
+ easting: number; // EPSG:2169
215
+ northing: number; // EPSG:2169
216
+ accuracy: number;
217
+ street?: string;
218
+ num?: string;
219
+ zip?: string;
220
+ locality?: string;
221
+ }
222
+ ```
223
+
224
+ ---
225
+
226
+ ## Coordinate utilities
227
+
228
+ The SDK exposes the internal coordinate conversion functions if you need them directly.
229
+
230
+ ```ts
231
+ import { latLonToLuref, lurefToLatLon } from '@dacostafilipe/react-geoportail';
232
+
233
+ // WGS84 → EPSG:2169 (Luxembourg TM)
234
+ const { easting, northing } = latLonToLuref(49.6116, 6.1319);
235
+
236
+ // EPSG:2169 → WGS84
237
+ const { lat, lon } = lurefToLatLon(76651, 75358);
238
+ ```
239
+
240
+ Accuracy is within ~1 m across Luxembourg's territory, using a Molodensky transformation (WGS84 → ED50) followed by a Transverse Mercator projection with the official LUREF parameters.
241
+
242
+ ---
243
+
244
+ ## Development
245
+
246
+ ```bash
247
+ # Install dependencies
248
+ npm install
249
+
250
+ # Start the demo app (Vite dev server)
251
+ npm run dev
252
+
253
+ # Type-check
254
+ npm run type-check
255
+
256
+ # Build the library (outputs to dist/)
257
+ npm run build
258
+ ```
259
+
260
+ The demo app (`src/demo/main.tsx`) shows all three features together: map with click-to-pin, reverse geocoding on pin placement, and an address search bar.
261
+
262
+ ---
263
+
264
+ ## API reference
265
+
266
+ - [Geoportail Luxembourg API v3 JSDoc](https://apiv3.geoportail.lu/proj/1.0/build/apidoc/)
267
+ - [REST API wiki](https://wiki.geoportail.lu/doku.php?id=en:api:rest)
268
+ - [Official API examples](https://apiv3.geoportail.lu/proj/1.0/build/apidoc/examples/)
269
+
270
+ ## License
271
+
272
+ MIT
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@dacostafilipe/react-geoportail",
3
+ "version": "0.1.1",
4
+ "description": "React SDK for the Geoportail Luxembourg v3 API",
5
+ "type": "module",
6
+ "main": "./dist/react-geoportail.umd.cjs",
7
+ "module": "./dist/react-geoportail.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/react-geoportail.js",
12
+ "require": "./dist/react-geoportail.umd.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "dev": "vite",
21
+ "build": "tsc -p tsconfig.build.json && vite build",
22
+ "preview": "vite preview",
23
+ "type-check": "tsc --noEmit"
24
+ },
25
+ "peerDependencies": {
26
+ "react": ">=17.0.0",
27
+ "react-dom": ">=17.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/react": "^18.2.0",
31
+ "@types/react-dom": "^18.2.0",
32
+ "@vitejs/plugin-react": "^4.2.0",
33
+ "typescript": "^5.3.0",
34
+ "vite": "^5.0.0",
35
+ "vite-plugin-dts": "^3.6.0"
36
+ },
37
+ "keywords": [
38
+ "geoportail",
39
+ "luxembourg",
40
+ "map",
41
+ "react",
42
+ "openlayers",
43
+ "geocode"
44
+ ],
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/dacostafilipe/react-geoportail.git"
49
+ },
50
+ "author": "Filipe DA COSTA",
51
+ "bugs": {
52
+ "url": "https://github.com/dacostafilipe/react-geoportail/issues"
53
+ },
54
+ "homepage": "https://github.com/dacostafilipe/react-geoportail#readme"
55
+ }