├── .gitattributes ├── .github └── CODEOWNERS ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── .yarn └── releases │ └── yarn-3.2.0.cjs ├── .yarnrc.yml ├── LICENSE ├── package.json ├── packages ├── react-openlayers-fiber │ ├── README.md │ ├── babel.config.cjs │ ├── cjs.swcrc │ ├── esm.swcrc │ ├── jest.config.js │ ├── jest.config.swc.js │ ├── next.config.js │ ├── package.json │ ├── src │ │ ├── __snapshots__ │ │ │ ├── test-rig.test.tsx.snap │ │ │ └── test-rig.vitest.tsx.snap │ │ ├── catalogue.ts │ │ ├── context.tsx │ │ ├── events.ts │ │ ├── index.ts │ │ ├── map.tsx │ │ ├── renderer.ts │ │ ├── test-rig.test.tsx │ │ ├── test-rig.vitest.tsx │ │ ├── test │ │ │ ├── setup.ts │ │ │ └── utils.tsx │ │ └── types.ts │ └── vitest.config.ts └── website │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages │ ├── _app.tsx │ ├── _middleware.tsx │ ├── docs │ │ ├── components.en-US.mdx │ │ ├── getting-started.en-US.mdx │ │ ├── hooks.en-US.mdx │ │ ├── imperative-fallback.en-US.mdx │ │ ├── meta.en-US.json │ │ └── props.en-US.mdx │ ├── examples │ │ ├── advanced │ │ │ ├── declaratively-move-view.en-US.mdx │ │ │ ├── dynamic-points.en-US.mdx │ │ │ ├── dynamic-styles.en-US.mdx │ │ │ ├── kitchen-sink.en-US.mdx │ │ │ ├── meta.en-US.json │ │ │ ├── performance.en-US.mdx │ │ │ └── retina.en-US.mdx │ │ ├── meta.en-US.json │ │ └── openlayers │ │ │ ├── accessible.en-US.mdx │ │ │ ├── bing-maps.en-US.mdx │ │ │ ├── cluster.en-US.mdx │ │ │ ├── draw-and-modify-features.en-US.mdx │ │ │ ├── draw-shapes.en-US.mdx │ │ │ ├── dynamic-data.en-US.mdx │ │ │ ├── geojson.en-US.mdx │ │ │ ├── icon.en-US.mdx │ │ │ ├── image-filter.en-US.mdx │ │ │ ├── mapbox-vector-tiles.en-US.mdx │ │ │ ├── meta.en-US.json │ │ │ ├── popup.en-US.mdx │ │ │ ├── preload.en-US.mdx │ │ │ ├── reprojection-image.en-US.mdx │ │ │ ├── reprojection-wgs84.en-US.mdx │ │ │ ├── select-features.en-US.mdx │ │ │ ├── simple.en-US.mdx │ │ │ ├── static-image.en-US.mdx │ │ │ ├── turf.en-US.mdx │ │ │ ├── wms-tiled.en-US.mdx │ │ │ ├── xyz-retina.en-US.mdx │ │ │ ├── xyz.en-US.mdx │ │ │ └── zoomify.en-US.mdx │ ├── index.en-US.mdx │ └── meta.en-US.json │ ├── postcss.config.js │ ├── public │ ├── favicon.ico │ ├── icon.png │ └── vercel.svg │ ├── styles │ └── globals.css │ ├── tailwind.config.js │ ├── theme.config.js │ └── tsconfig.json ├── readme.md ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @crubier 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Diagnostic reports (https://nodejs.org/api/report.html) 6 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | *.lcov 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Dependency directories 25 | node_modules/ 26 | jspm_packages/ 27 | 28 | # TypeScript cache 29 | *.tsbuildinfo 30 | 31 | # Optional npm cache directory 32 | .npm 33 | 34 | # Optional eslint cache 35 | .eslintcache 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | # Output of 'npm pack' 41 | *.tgz 42 | 43 | # Yarn Integrity file 44 | .yarn-integrity 45 | 46 | # MacOS 47 | .DS_Store 48 | 49 | # vercel 50 | .vercel 51 | 52 | # next.js 53 | .next 54 | out 55 | 56 | .yarn/* 57 | !.yarn/patches 58 | !.yarn/plugins 59 | !.yarn/releases 60 | !.yarn/sdks 61 | !.yarn/versions 62 | 63 | dist -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | ], 6 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 7 | "unwantedRecommendations": [] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "node", 5 | "name": "vscode-jest-tests", 6 | "request": "launch", 7 | "console": "integratedTerminal", 8 | "internalConsoleOptions": "neverOpen", 9 | "disableOptimisticBPs": true, 10 | "cwd": "${workspaceFolder}", 11 | "runtimeExecutable": "yarn", 12 | "args": ["test", "--runInBand", "--watchAll=false"], 13 | "outFiles": [ 14 | "${workspaceFolder}/packages/*/src/**/*.ts?(x)", 15 | "!**/node_modules/**" 16 | ] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.formatOnSave": false, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true 6 | } 7 | }, 8 | "[typescriptreact]": { 9 | "editor.formatOnSave": false, 10 | "editor.codeActionsOnSave": { 11 | "source.fixAll.eslint": true 12 | } 13 | }, 14 | "eslint.workingDirectories": [{ "mode": "auto" }], 15 | "cSpell.words": [ 16 | "Heatmap", 17 | "Mapbox", 18 | "WMTS", 19 | "Zoomify", 20 | "openlayers", 21 | "upsert" 22 | ], 23 | "jest.jestCommandLine": "yarn test", 24 | "gitlens.views.commits.files.layout": "tree", 25 | "gitlens.views.repositories.files.layout": "tree", 26 | "gitlens.views.fileHistory.files.layout": "tree", 27 | "gitlens.views.branches.files.layout": "tree", 28 | "gitlens.views.remotes.files.layout": "tree", 29 | "gitlens.views.stashes.files.layout": "tree", 30 | "gitlens.views.tags.files.layout": "tree", 31 | "gitlens.views.worktrees.files.layout": "tree", 32 | "gitlens.views.contributors.files.layout": "tree", 33 | "gitlens.views.searchAndCompare.files.layout": "tree" 34 | } 35 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | defaultSemverRangePrefix: "" 2 | 3 | logFilters: 4 | - code: YN0002 5 | level: error 6 | - code: YN0060 7 | level: warning 8 | 9 | nodeLinker: node-modules 10 | 11 | yarnPath: .yarn/releases/yarn-3.2.0.cjs 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present Vincent Lecrubier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-ol/root", 3 | "version": "1.0.0", 4 | "repository": "git@github.com:crubier/crubier/react-openlayers-fiber.git", 5 | "engines": { 6 | "node": ">=14.0.0", 7 | "yarn": ">=1.0.0" 8 | }, 9 | "workspaces": [ 10 | "packages/*" 11 | ], 12 | "packageManager": "yarn@3.2.0" 13 | } 14 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/README.md: -------------------------------------------------------------------------------- 1 | # React OpenLayers Fiber 2 | 3 | `@react-ol/fiber` is a react renderer for OpenLayers https://openlayers.org/ . 4 | 5 | It is based on the same concept as what `@react-three/fiber` does for Three.js. 6 | 7 | Visit https://react-openlayers-fiber.vercel.app 8 | 9 | ## What does it look like? 10 | 11 | ```tsx 12 | import React from "react"; 13 | import "ol/ol.css"; 14 | import { Map } from "@react-ol/fiber"; 15 | 16 | export const Example = () => ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | ``` 25 | 26 | ## Why? 27 | 28 | Build your maps declaratively with re-usable, self-contained components that react to state, are readily interactive and can tap into React's ecosystem. 29 | 30 | ### Does it have limitations? 31 | 32 | None. Everything that works in OpenLayers will work here without exception. 33 | 34 | ### Can it keep up with frequent updates to OpenLayers? 35 | 36 | Yes. There is no hard dependency on a particular OpenLayers version, it does not wrap or duplicate a single OpenLayers class. It merely expresses OpenLayers in JSX: `` becomes `new ol.View()`, and that happens dynamically. 37 | 38 | ### Is it slower than plain OpenLayers? 39 | 40 | No. There is no additional overhead. Components participate in a unified renderloop outside of React. It outperforms OpenLayers at scale due to React's scheduling abilities. 41 | 42 | ## Fundamentals 43 | 44 | You need to be versed in both React and OpenLayers before rushing into this. If you are unsure about React consult the official React docs, especially the section about hooks. As for OpenLayers, make sure you at least glance over the following links: 45 | 46 | - Make sure you have a [basic grasp of OpenLayers](https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html). Keep that site open. 47 | - When you know what a `map` is, a `view`, `layer`, `source`, `geometry`, fork the [demo](/#what-does-it-look-like) above. 48 | - [Look up](https://openlayers.org/en/latest/apidoc/module-ol_layer_Layer-Layer.html) the JSX elements that you see (`layer`, `source`, etc), all OpenLayers exports are native to `@react-ol/fiber`. 49 | - Try changing some values, scroll through our API to see what the various settings and hooks do. 50 | 51 | ## Ecosystem 52 | 53 | - [`@react-three/fiber`](https://github.com/pmndrs/react-three-fiber) – a similar library, targeting Three.js 54 | - [`zustand`](https://github.com/pmndrs/zustand) – state management 55 | - [`react-spring`](https://github.com/pmndrs/react-spring) – a spring-physics-based animation library 56 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "presets": [ 3 | // [ 4 | // "@babel/preset-typescript", 5 | // { 6 | // "isTSX": true, 7 | // "allExtensions": true 8 | // } 9 | // ], 10 | ["@babel/preset-react", {}], 11 | ["@babel/preset-env", { "targets": { "node": "current" } }] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/cjs.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "type": "commonjs", 4 | "strict": false, 5 | "strictMode": true, 6 | "lazy": false, 7 | "noInterop": false 8 | }, 9 | "jsc": { 10 | "parser": { 11 | "syntax": "typescript", 12 | "tsx": true, 13 | "decorators": true, 14 | "dynamicImport": true 15 | }, 16 | "target": "es5" 17 | }, 18 | "minify": false 19 | } 20 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/esm.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "type": "es6", 4 | "strict": false, 5 | "strictMode": true, 6 | "lazy": false, 7 | "noInterop": false 8 | }, 9 | "jsc": { 10 | "parser": { 11 | "syntax": "typescript", 12 | "tsx": true, 13 | "decorators": true, 14 | "dynamicImport": true 15 | }, 16 | "target": "es2016" 17 | }, 18 | "minify": false 19 | } 20 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | preset: 'ts-jest', 8 | testEnvironment: "jsdom", 9 | transform: { "^.+\\.(js|jsx|ts|tsx|mjs)$": "babel-jest" }, 10 | transformIgnorePatterns: [ 11 | '^.+\\.module\\.(css|sass|scss)$', 12 | // '^.*/node_modules/(?!(ol/|labelgun/|@mapbox/mapbox-gl-style-spec/|mapbox-to-ol-style/|ol-mapbox-style/|geotiff/|fetch-blob/)).*$' 13 | '/node_modules/(?!(ol/))' 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/jest.config.swc.js: -------------------------------------------------------------------------------- 1 | // jest.config.js 2 | import nextJest from 'next/jest.js'; 3 | 4 | const createJestConfig = nextJest({ 5 | // Provide the path to your Next.js app to load next.config.js and .env files in your test environment 6 | dir: '.', 7 | }) 8 | 9 | // Add any custom config to be passed to Jest 10 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 11 | const customJestConfig = { 12 | // preset: 'ts-jest', 13 | // Add more setup options before each test is run 14 | // setupFilesAfterEnv: ['/jest.setup.js'], 15 | // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work 16 | // moduleDirectories: ['node_modules', '/'], 17 | testEnvironment: 'jest-environment-jsdom', 18 | } 19 | 20 | const jestConfig = async () => { 21 | const nextJestConfig = await createJestConfig(customJestConfig)(); 22 | // See https://github.com/vercel/next.js/issues/35634#issuecomment-1080942525 23 | const veryCustomJestConfig = { 24 | ...nextJestConfig, 25 | transformIgnorePatterns: [ 26 | '^.+\\.module\\.(css|sass|scss)$', 27 | // '/node_modules/(?!(ol/|labelgun/|@mapbox/mapbox-gl-style-spec/|mapbox-to-ol-style/|ol-mapbox-style/|geotiff/|fetch-blob/))' 28 | '/node_modules/(?!(ol/))' 29 | ] 30 | }; 31 | return veryCustomJestConfig; 32 | }; 33 | 34 | 35 | // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async 36 | export default jestConfig; -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | // Prefer loading of ES Modules over CommonJS 5 | // @link {https://nextjs.org/blog/next-11-1#es-modules-support|Blog 11.1.0} 6 | // @link {https://github.com/vercel/next.js/discussions/27876|Discussion} 7 | esmExternals: true, 8 | // Experimental monorepo support 9 | // @link {https://github.com/vercel/next.js/pull/22867|Original PR} 10 | // @link {https://github.com/vercel/next.js/discussions/26420|Discussion} 11 | externalDir: true, 12 | }, 13 | reactStrictMode: true, 14 | } 15 | 16 | export default nextConfig; 17 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-ol/fiber", 3 | "version": "1.0.0-beta.1", 4 | "sideEffects": false, 5 | "description": "React OpenLayers bindings using react fiber", 6 | "license": "MIT", 7 | "author": "Vincent Lecrubier ", 8 | "main": "./src/index.ts", 9 | "module": "./src/index.ts", 10 | "types": "./src/index.ts", 11 | "type": "module", 12 | "publishConfig": { 13 | "access": "public", 14 | "main": "./dist/cjs/index.js", 15 | "module": "./dist/esm/index.js", 16 | "types": "./dist/ts/index.d.ts" 17 | }, 18 | "files": [ 19 | "dist/**/*", 20 | "src/**/*" 21 | ], 22 | "scripts": { 23 | "type-check": "tsc", 24 | "vitest": "vitest", 25 | "jest": "jest", 26 | "jestswc": "jest --config jest.config.swc.js", 27 | "build": "swc ./src --out-dir ./dist/esm --config-file ./esm.swcrc && swc ./src --out-dir ./dist/cjs --config-file ./cjs.swcrc && tsc -d --declarationDir dist/ts --declarationMap --emitDeclarationOnly" 28 | }, 29 | "dependencies": { 30 | "lodash": "4.17.21", 31 | "react-reconciler": "0.25.1", 32 | "scheduler": "0.20.1" 33 | }, 34 | "peerDependencies": { 35 | "ol": "*", 36 | "react": "*", 37 | "react-dom": "*" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "7.17.8", 41 | "@babel/preset-env": "7.16.11", 42 | "@babel/preset-react": "7.16.7", 43 | "@babel/preset-typescript": "7.16.7", 44 | "@swc/cli": "0.1.56", 45 | "@swc/core": "1.2.159", 46 | "@testing-library/jest-dom": "5.16.3", 47 | "@testing-library/react": "12.1.4", 48 | "@testing-library/user-event": "14.0.2", 49 | "@types/jest": "24.0.13", 50 | "@types/lodash": "4.14.154", 51 | "@types/proj4": "2.5.0", 52 | "@types/react": "17.0.0", 53 | "@types/react-dom": "17.0.0", 54 | "@types/react-reconciler": "0.26.1", 55 | "@vitejs/plugin-react": "1.3.0", 56 | "babel-jest": "27.5.1", 57 | "jest": "27.5.1", 58 | "next": "12.1.1", 59 | "ol": "6.13.0", 60 | "react": "latest", 61 | "react-dom": "latest", 62 | "ts-jest": "27.1.4", 63 | "tsc": "2.0.4", 64 | "typescript": "4.3.5", 65 | "vitest": "0.8.1" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/src/__snapshots__/test-rig.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Test setup > can run dom snapshot test 1`] = ` 4 | 5 | 6 | ok 7 | 8 | 9 | `; 10 | 11 | exports[`Test setup can run dom snapshot test 1`] = ` 12 | 13 | 14 | ok 15 | 16 | 17 | `; 18 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/src/__snapshots__/test-rig.vitest.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`Test setup > can run dom snapshot test 1`] = ` 4 | 5 | 6 | ok 7 | 8 | 9 | `; 10 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/src/catalogue.ts: -------------------------------------------------------------------------------- 1 | // ///////////////////////////////////////////////////////////////////////////// 2 | // The catalogue is the list of object from openlayers that are supported 3 | // it is generated automatically, and is strongly typed 4 | // ///////////////////////////////////////////////////////////////////////////// 5 | 6 | import { 7 | upperFirst, 8 | lowerFirst, 9 | omit, 10 | fromPairs, 11 | toPairs, 12 | map, 13 | isObject, 14 | } from "lodash/fp"; 15 | 16 | // ///////////////////////////////////////////////////////////////////////////// 17 | // Import stuff we want to use from Openlayers 18 | 19 | import * as olRaw from "ol"; 20 | import * as olLayerRaw from "ol/layer"; 21 | import * as olControlRaw from "ol/control"; 22 | import * as olInteractionRaw from "ol/interaction"; 23 | import * as olSourceRaw from "ol/source"; 24 | import * as olGeomRaw from "ol/geom"; 25 | import * as olStyleRaw from "ol/style"; 26 | 27 | 28 | // ///////////////////////////////////////////////////////////////////////////// 29 | // Here we define what we omit: abstract base classes, utility classes and other weird stuff 30 | 31 | const olOmitKeys = [ 32 | "defaults", 33 | "AssertionError", 34 | "Disposable", 35 | "Graticule", 36 | "Image", 37 | "ImageBase", 38 | "ImageCanvas", 39 | "ImageTile", 40 | "Kinetic", 41 | "MapBrowserEvent", 42 | "MapBrowserEventHandler", 43 | "MapEvent", 44 | "Tile", 45 | "TileQueue", 46 | "TileRange", 47 | "VectorRenderTile", 48 | "VectorTile", 49 | "getUid", 50 | "VERSION", 51 | ] as const; 52 | const olLayerOmitKeys = [] as const; 53 | const olControlOmitKeys = ["defaults"] as const; 54 | const olInteractionOmitKeys = ["defaults"] as const; 55 | const olSourceOmitKeys = ["Image", "Source", "Tile", "sourcesFromTileGrid"] as const; 56 | const olGeomOmitKeys = ["Geometry", "SimpleGeometry"] as const; 57 | const olStyleOmitKeys = ["Image", "IconImage"] as const; 58 | 59 | // ///////////////////////////////////////////////////////////////////////////// 60 | // Here we do omit things listed above 61 | 62 | const ol = omit(olOmitKeys, olRaw) as Omit< 63 | typeof olRaw, 64 | typeof olOmitKeys[number] 65 | >; 66 | const olLayer = omit(olLayerOmitKeys, olLayerRaw) as Omit< 67 | typeof olLayerRaw, 68 | typeof olLayerOmitKeys[number] 69 | >; 70 | const olControl = omit(olControlOmitKeys, olControlRaw) as Omit< 71 | typeof olControlRaw, 72 | typeof olControlOmitKeys[number] 73 | >; 74 | const olInteraction = omit(olInteractionOmitKeys, olInteractionRaw) as Omit< 75 | typeof olInteractionRaw, 76 | typeof olInteractionOmitKeys[number] 77 | >; 78 | const olSource = omit(olSourceOmitKeys, olSourceRaw) as Omit< 79 | typeof olSourceRaw, 80 | typeof olSourceOmitKeys[number] 81 | >; 82 | const olGeom = omit(olGeomOmitKeys, olGeomRaw) as Omit< 83 | typeof olGeomRaw, 84 | typeof olGeomOmitKeys[number] 85 | >; 86 | const olStyle = omit(olStyleOmitKeys, olStyleRaw) as Omit< 87 | typeof olStyleRaw, 88 | typeof olStyleOmitKeys[number] 89 | >; 90 | 91 | // ///////////////////////////////////////////////////////////////////////////// 92 | 93 | // type OlSourceRaw = typeof olSourceRaw; 94 | // // type OlSourceRawKey = keyof OlSourceRaw; 95 | // type OlSourceOmitKey = typeof olSourceOmitKeys[number]; 96 | // // type OlSourceKey = Exclude; 97 | // // type OlSourceElement = OlSourceRaw[OlSourceKey]; 98 | // type OlSource = Omit; 99 | 100 | // const olSource = Object.fromEntries( 101 | // Object.entries(olSourceRaw).filter( 102 | // ([key]) => !olSourceOmitKeys.includes(key as OlSourceOmitKey) 103 | // ) // as [OlSourceKey, OlSourceElement] 104 | // ) as OlSource; 105 | 106 | // ///////////////////////////////////////////////////////////////////////////// 107 | // Now we generate the types automatically (This parts needs typescript >=4.1) 108 | 109 | type CatalogueOl = { 110 | [K in keyof typeof ol as `ol${Capitalize}`]: { 111 | kind: K; 112 | type: `ol${Capitalize}`; 113 | object: typeof ol[K]; 114 | }; 115 | }; 116 | type CatalogueOlLayer = { 117 | [K in keyof typeof olLayer as `olLayer${Capitalize}`]: { 118 | kind: "Layer"; 119 | type: `olLayer${Capitalize}`; 120 | object: typeof olLayer[K]; 121 | }; 122 | }; 123 | type CatalogueOlControl = { 124 | [K in keyof typeof olControl as `olControl${Capitalize}`]: { 125 | kind: "Control"; 126 | type: `olControl${Capitalize}`; 127 | object: typeof olControl[K]; 128 | }; 129 | }; 130 | type CatalogueOlInteraction = { 131 | [K in keyof typeof olInteraction as `olInteraction${Capitalize}`]: { 132 | kind: "Interaction"; 133 | type: `olInteraction${Capitalize}`; 134 | object: typeof olInteraction[K]; 135 | }; 136 | }; 137 | type CatalogueOlSource = { 138 | [K in keyof typeof olSource as `olSource${Capitalize}`]: { 139 | kind: "Source"; 140 | type: `olSource${Capitalize}`; 141 | object: typeof olSource[K]; 142 | }; 143 | }; 144 | type CatalogueOlGeom = { 145 | [K in keyof typeof olGeom as `olGeom${Capitalize}`]: { 146 | kind: "Geom"; 147 | type: `olGeom${Capitalize}`; 148 | object: typeof olGeom[K]; 149 | }; 150 | }; 151 | type CatalogueOlStyle = { 152 | [K in keyof typeof olStyle as `olStyle${Capitalize}`]: { 153 | kind: "Style"; 154 | type: `olStyle${Capitalize}`; 155 | object: typeof olStyle[K]; 156 | }; 157 | }; 158 | 159 | // ///////////////////////////////////////////////////////////////////////////// 160 | // Finished, now some additional stuff 161 | 162 | export type Catalogue = CatalogueOl & 163 | CatalogueOlLayer & 164 | CatalogueOlControl & 165 | CatalogueOlInteraction & 166 | CatalogueOlSource & 167 | CatalogueOlGeom & 168 | CatalogueOlStyle; 169 | 170 | 171 | export type CatalogueKey = keyof Catalogue; 172 | export type CatalogueItem = Catalogue[CatalogueKey]; 173 | export type Kind = CatalogueItem["kind"]; 174 | export type ExtendedCatalogueItem = { 175 | object: T; 176 | kind: Kind | null; 177 | type: string; 178 | }; 179 | 180 | // ///////////////////////////////////////////////////////////////////////////// 181 | // Catalogue Values 182 | 183 | const catalogueOl = Object.fromEntries( 184 | Object.entries(ol).map(([key, value]) => [ 185 | `ol${upperFirst(key)}`, 186 | { 187 | kind: key, 188 | type: `ol${upperFirst(key)}`, 189 | object: value, 190 | }, 191 | ]) 192 | ) as CatalogueOl; 193 | 194 | const catalogueOlLayer = Object.fromEntries( 195 | Object.entries(olLayer).map(([key, value]) => [ 196 | `olLayer${upperFirst(key)}`, 197 | { 198 | kind: "Layer", 199 | type: `olLayer${upperFirst(key)}`, 200 | object: value, 201 | }, 202 | ]) 203 | ) as CatalogueOlLayer; 204 | 205 | const catalogueOlControl = Object.fromEntries( 206 | Object.entries(olControl).map(([key, value]) => [ 207 | `olControl${upperFirst(key)}`, 208 | { 209 | kind: "Control", 210 | type: `olControl${upperFirst(key)}`, 211 | object: value, 212 | }, 213 | ]) 214 | ) as CatalogueOlControl; 215 | 216 | const catalogueOlInteraction = Object.fromEntries( 217 | Object.entries(olInteraction).map(([key, value]) => [ 218 | `olInteraction${upperFirst(key)}`, 219 | { 220 | kind: "Interaction", 221 | type: `olInteraction${upperFirst(key)}`, 222 | object: value, 223 | }, 224 | ]) 225 | ) as CatalogueOlInteraction; 226 | 227 | const catalogueOlSource = Object.fromEntries( 228 | Object.entries(olSource).map(([key, value]) => [ 229 | `olSource${upperFirst(key)}`, 230 | { 231 | kind: "Source", 232 | type: `olSource${upperFirst(key)}`, 233 | object: value, 234 | }, 235 | ]) 236 | ) as CatalogueOlSource; 237 | 238 | const catalogueOlGeom = Object.fromEntries( 239 | Object.entries(olGeom).map(([key, value]) => [ 240 | `olGeom${upperFirst(key)}`, 241 | { 242 | kind: "Geom", 243 | type: `olGeom${upperFirst(key)}`, 244 | object: value, 245 | }, 246 | ]) 247 | ) as CatalogueOlGeom; 248 | 249 | const catalogueOlStyle = Object.fromEntries( 250 | Object.entries(olStyle).map(([key, value]) => [ 251 | `olStyle${upperFirst(key)}`, 252 | { 253 | kind: "Style", 254 | type: `olStyle${upperFirst(key)}`, 255 | object: value, 256 | }, 257 | ]) 258 | ) as CatalogueOlStyle; 259 | 260 | // eslint-disable-next-line import/no-mutable-exports 261 | export let catalogue: Catalogue = { 262 | ...catalogueOl, 263 | ...catalogueOlLayer, 264 | ...catalogueOlControl, 265 | ...catalogueOlInteraction, 266 | ...catalogueOlSource, 267 | ...catalogueOlGeom, 268 | ...catalogueOlStyle, 269 | }; 270 | 271 | /// //////////////////////////////////////////////////////////////////////// 272 | 273 | /// //////////////////////////////////////////////////////////////////////////// 274 | // A way to extend the catalogue 275 | export const extend = (objects: { [key: string]: T }): void => { 276 | // Cleanup the input 277 | const cleanedUpObjects = fromPairs( 278 | map(([key, value]: [string, U | ExtendedCatalogueItem]): [ 279 | string, 280 | ExtendedCatalogueItem 281 | ] => { 282 | if (!isObject((value as ExtendedCatalogueItem).object)) { 283 | // If it's directly an object we put it nicely in a catalogue item 284 | return [ 285 | lowerFirst(key), 286 | { 287 | type: lowerFirst(key), 288 | kind: null, 289 | object: value as U, 290 | }, 291 | ]; 292 | } 293 | // If it's already a catalogue item it's good 294 | return [ 295 | lowerFirst(key), 296 | { 297 | ...(value as ExtendedCatalogueItem), 298 | type: lowerFirst(key), 299 | }, 300 | ]; 301 | }, toPairs(objects)) 302 | ); 303 | // Update the catalogue 304 | catalogue = { ...catalogue, ...cleanedUpObjects }; 305 | }; 306 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/src/context.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from "react"; 2 | import { Map as OlMap } from "ol"; 3 | 4 | export const MapContext = createContext(null); 5 | 6 | export const useMap = () => useContext(MapContext); 7 | export const MapConsumer = MapContext.Consumer; 8 | export const MapProvider = MapContext.Provider; 9 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/src/events.ts: -------------------------------------------------------------------------------- 1 | // List of possible listeners generated in a dirty way 2 | // Generated using 3 | // grep "on(type: '" -R ./node_modules/@types/ol 4 | // and then doing some CMD-D magic in VSCode... 5 | // Had to do this because of https://github.com/microsoft/TypeScript/issues/40816 6 | import { ListenerFunction } from "ol/events"; 7 | import { DrawEvent } from "ol/interaction/Draw"; 8 | import { ModifyEvent } from "ol/interaction/Modify"; 9 | import { SelectEvent } from "ol/interaction/Select"; 10 | import { TranslateEvent } from "ol/interaction/Translate"; 11 | import RenderEvent from "ol/render/Event"; 12 | 13 | export type RenderListenerFunction = (p0: RenderEvent) => boolean; 14 | 15 | export type Events = Partial<{ 16 | onAdd: ListenerFunction; 17 | onAddfeature: ListenerFunction; 18 | onAddfeatures: ListenerFunction; 19 | onAfteroperations: ListenerFunction; 20 | onBeforeoperations: ListenerFunction; 21 | onBoxdrag: ListenerFunction; 22 | onBoxend: ListenerFunction; 23 | onBoxstart: ListenerFunction; 24 | onChange_accuracy: ListenerFunction; 25 | onChange_accuracyGeometry: ListenerFunction; 26 | onChange_active: ListenerFunction; 27 | onChange_altitude: ListenerFunction; 28 | onChange_altitudeAccuracy: ListenerFunction; 29 | onChange_blur: ListenerFunction; 30 | onChange_center: ListenerFunction; 31 | onChange_coordinateFormat: ListenerFunction; 32 | onChange_element: ListenerFunction; 33 | onChange_extent: ListenerFunction; 34 | onChange_geometry: ListenerFunction; 35 | onChange_gradient: ListenerFunction; 36 | onChange_heading: ListenerFunction; 37 | onChange_layerGroup: ListenerFunction; 38 | onChange_layers: ListenerFunction; 39 | onChange_length: ListenerFunction; 40 | onChange_map: ListenerFunction; 41 | onChange_maxResolution: ListenerFunction; 42 | onChange_maxZoom: ListenerFunction; 43 | onChange_minResolution: ListenerFunction; 44 | onChange_minZoom: ListenerFunction; 45 | onChange_offset: ListenerFunction; 46 | onChange_opacity: ListenerFunction; 47 | onChange_position: ListenerFunction; 48 | onChange_positioning: ListenerFunction; 49 | onChange_preload: ListenerFunction; 50 | onChange_projection: ListenerFunction; 51 | onChange_radius: ListenerFunction; 52 | onChange_resolution: ListenerFunction; 53 | onChange_rotation: ListenerFunction; 54 | onChange_size: ListenerFunction; 55 | onChange_source: ListenerFunction; 56 | onChange_speed: ListenerFunction; 57 | onChange_target: ListenerFunction; 58 | onChange_tracking: ListenerFunction; 59 | onChange_trackingOptions: ListenerFunction; 60 | onChange_units: ListenerFunction; 61 | onChange_useInterimTilesOnError: ListenerFunction; 62 | onChange_view: ListenerFunction; 63 | onChange_visible: ListenerFunction; 64 | onChange_zIndex: ListenerFunction; 65 | onChange: ListenerFunction; 66 | onChangefeature: ListenerFunction; 67 | onClear: ListenerFunction; 68 | onClick: ListenerFunction; 69 | onDblclick: ListenerFunction; 70 | onDrawabort: ListenerFunction; 71 | onDrawend: (drawEvent: DrawEvent) => void; 72 | onDrawstart: ListenerFunction; 73 | onEnterfullscreen: ListenerFunction; 74 | onError: ListenerFunction; 75 | onExtentchanged: ListenerFunction; 76 | onImageloadend: ListenerFunction; 77 | onImageloaderror: ListenerFunction; 78 | onImageloadstart: ListenerFunction; 79 | onLeavefullscreen: ListenerFunction; 80 | onModifyend: (event: ModifyEvent) => boolean | Promise; 81 | onModifystart: ListenerFunction; 82 | onMoveend: ListenerFunction; 83 | onMovestart: ListenerFunction; 84 | onPointerdrag: ListenerFunction; 85 | onPointermove: ListenerFunction; 86 | onPostcompose: ListenerFunction; 87 | onPostrender: RenderListenerFunction; 88 | onPrecompose: ListenerFunction; 89 | onPrerender: RenderListenerFunction; 90 | onPropertychange: ListenerFunction; 91 | onRemove: ListenerFunction; 92 | onRemovefeature: ListenerFunction; 93 | onRendercomplete: ListenerFunction; 94 | onSelect: (selectEvent: SelectEvent) => void; 95 | onSingleclick: ListenerFunction; 96 | onTileloadend: ListenerFunction; 97 | onTileloaderror: ListenerFunction; 98 | onTileloadstart: ListenerFunction; 99 | onTranslateend: (event: TranslateEvent) => boolean | Promise; 100 | onTranslatestart: ListenerFunction; 101 | onTranslating: ListenerFunction; 102 | }>; 103 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Catalogue as CatalogueBase, 3 | CatalogueKey as CatalogueKeyBase, 4 | Kind as KindBase, 5 | } from "./catalogue"; 6 | 7 | export { catalogue, extend } from "./catalogue"; 8 | 9 | export type Catalogue = CatalogueBase; 10 | 11 | export type CatalogueKey = CatalogueKeyBase; 12 | export type Kind = KindBase; 13 | 14 | export { MapContext, MapProvider, MapConsumer, useMap } from "./context"; 15 | 16 | export { Map } from "./map"; 17 | 18 | export * from "./types"; 19 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/src/map.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | MutableRefObject, 3 | useRef, 4 | useEffect, 5 | useState, 6 | forwardRef, 7 | } from "react"; 8 | import { Map as OlMap } from "ol"; 9 | import { isNull, isFunction, isNil } from "lodash/fp"; 10 | import { render } from "./renderer"; 11 | 12 | import { MapProvider } from "./context"; 13 | 14 | import { ReactOlFiber } from "./types"; 15 | 16 | const defaultArgs = [{}] as [ConstructorParameters[0]]; 17 | const defaultStyle = { width: "100%", height: "400px" }; 18 | 19 | export type Props = ReactOlFiber.IntrinsicElements["olMap"] & { 20 | style?: React.CSSProperties; 21 | containerRef?: React.Ref; 22 | }; 23 | 24 | export const Map = forwardRef( 25 | ( 26 | { 27 | children, 28 | args = defaultArgs, 29 | style = defaultStyle, 30 | className, 31 | containerRef, 32 | ...mapProps 33 | }: Props, 34 | ref 35 | ): React.ReactElement => { 36 | const mapContainerRef = useRef(null); 37 | const [map, setMap] = useState(null); 38 | 39 | useEffect(() => { 40 | if (mapContainerRef.current) { 41 | const wrapped = ( 42 | 48 | {isNull(map) ? null : ( 49 | {children} 50 | )} 51 | 52 | ); 53 | const returnedMap = render(wrapped, mapContainerRef.current) as OlMap; 54 | 55 | if (isNull(map) && !isNull(returnedMap)) { 56 | setMap((oldMap) => (isNull(oldMap) ? returnedMap : oldMap)); 57 | } 58 | } 59 | }, [children, mapContainerRef.current, map]); 60 | 61 | const setRef = (value: HTMLDivElement): void => { 62 | if (isNil(value)) { 63 | return; 64 | } 65 | if (isFunction(containerRef)) { 66 | containerRef(value); 67 | } else if (!isNil(containerRef)) { 68 | // eslint-disable-next-line no-param-reassign 69 | (containerRef as MutableRefObject).current = value; 70 | } 71 | if (isFunction(mapContainerRef)) { 72 | mapContainerRef(value); 73 | } else if (!isNil(mapContainerRef)) { 74 | // eslint-disable-next-line no-param-reassign 75 | (mapContainerRef as MutableRefObject).current = value; 76 | } 77 | }; 78 | 79 | return ; 80 | } 81 | ); 82 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/src/test-rig.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "@testing-library/react"; 3 | import { Map } from "ol"; 4 | 5 | 6 | describe("Test setup", () => { 7 | it("can run basic test", () => { 8 | expect(1).toBe(1); 9 | }); 10 | it("can run inline snapshot test", () => { 11 | expect(1).toMatchInlineSnapshot(`1`); 12 | }); 13 | it("can run dom snapshot test", () => { 14 | const { container } = render(ok); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | it("can run react testing library test", () => { 18 | const { getByText } = render(ok); 19 | const heading = getByText(/ok/i); 20 | console.log(heading); 21 | expect(heading).toMatchInlineSnapshot(` 22 | 23 | ok 24 | 25 | `); 26 | }); 27 | it("can run ol tests", () => { 28 | const map = new Map({}); 29 | expect(map.getAllLayers().length).toBe(0); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/src/test-rig.vitest.tsx: -------------------------------------------------------------------------------- 1 | import { render,screen, userEvent } from "./test/utils"; 2 | import { Map } from "ol"; 3 | import React from "react"; 4 | import { describe, expect, it } from 'vitest' 5 | 6 | 7 | 8 | // describe('Simple working test', () => { 9 | // it('the title is visible', () => { 10 | // render() 11 | // expect(screen.getByText(/Hello Vite \+ React!/i)).toBeInTheDocument() 12 | // }) 13 | 14 | // it('should increment count on click', async() => { 15 | // render() 16 | // userEvent.click(screen.getByRole('button')) 17 | // expect(await screen.findByText(/count is: 1/i)).toBeInTheDocument() 18 | // }) 19 | // }) 20 | 21 | 22 | 23 | describe("Test setup", () => { 24 | it("can run basic test", () => { 25 | expect(1).toBe(1); 26 | }); 27 | it("can run inline snapshot test", () => { 28 | expect(1).toMatchInlineSnapshot(`1`); 29 | }); 30 | it("can run dom snapshot test", () => { 31 | const { container } = render(ok); 32 | expect(container).toMatchSnapshot(); 33 | }); 34 | it("can run react testing library test", () => { 35 | const { getByText } = render(ok); 36 | const heading = getByText(/ok/i); 37 | expect(heading).toMatchInlineSnapshot(` 38 | 39 | ok 40 | 41 | `); 42 | }); 43 | it("can run ol tests", () => { 44 | const map = new Map({}); 45 | expect(map.getAllLayers().length).toBe(0); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/src/test/setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom' 2 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/src/test/utils.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/export */ 2 | import { render } from '@testing-library/react' 3 | 4 | const customRender = (ui: React.ReactElement, options = {}) => 5 | render(ui, { 6 | // wrap provider(s) here if needed 7 | wrapper: ({ children }) => children, 8 | ...options, 9 | }) 10 | 11 | export * from '@testing-library/react' 12 | export { default as userEvent } from '@testing-library/user-event' 13 | // override render export 14 | export { customRender as render } -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/src/types.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Catalogue } from "./catalogue"; 4 | import { Events } from "./events"; 5 | 6 | // eslint-disable-next-line @typescript-eslint/no-namespace 7 | export declare namespace ReactOlFiber { 8 | type IfEquals = (() => T extends X 9 | ? 1 10 | : 2) extends () => T extends Y ? 1 : 2 11 | ? A 12 | : B; 13 | 14 | /** 15 | * Get keys whose fields are writable in a class (not readonly) 16 | */ 17 | type WritableKeys = { 18 | [P in keyof T]-?: IfEquals< 19 | { [Q in P]: T[P] }, 20 | { -readonly [Q in P]: T[P] }, 21 | P 22 | >; 23 | }[keyof T]; 24 | 25 | /** 26 | * Get keys whose fields are functions in a class 27 | */ 28 | type FunctionKeys = { 29 | [K in keyof T]: T[K] extends Function ? K : never; 30 | }[keyof T]; 31 | 32 | /** 33 | * Get keys whose fields are classes in a class 34 | */ 35 | type ClassKeys = { 36 | [K in keyof T]: T[K] extends new (...args: any) => any ? K : never; 37 | }[keyof T]; 38 | 39 | /** 40 | * Get keys whose fields are ol object catalog elements class with a single option arg in constructor 41 | */ 42 | type OlObjectCatalogElementKeys = { 43 | [K in keyof T]: T[K] extends { 44 | object: new (arg: any, arg2: void) => any /* new (arg?: U) => any */; 45 | } 46 | ? K 47 | : never; 48 | }[keyof T]; 49 | 50 | type PickWritables = Pick>; 51 | type OmitWritables = Omit>; 52 | 53 | type PickFunctions = Pick>; 54 | type OmitFunctions = Omit>; 55 | 56 | type PickClasses = Pick>; 57 | type OmitClasses = Omit>; 58 | 59 | type SimpleObjectKeys = Pick>; 60 | type ComplexObjectKeys = Omit>; 61 | // type WeirdObjectKeys = Pick< 62 | // ComplexObjectKeys, 63 | // | "olSourceImage" 64 | // | "olSourceSource" 65 | // | "olSourceTile" 66 | // | "olGeomGeometry" 67 | // | "olGeomSimpleGeometry" 68 | // >; 69 | // type MultipleObjectKeys = Omit< 70 | // ComplexObjectKeys, 71 | // | "olSourceImage" 72 | // | "olSourceSource" 73 | // | "olSourceTile" 74 | // | "olGeomGeometry" 75 | // | "olGeomSimpleGeometry" 76 | // >; 77 | 78 | /** 79 | * Generic elements based on simple ol objects (most usual case) 80 | */ 81 | type IntrinsicElementsSimpleObject = { 82 | [T in keyof SimpleObjectKeys]: Partial< 83 | // Fields of the class that are not functions (Most of the time there isn't any) 84 | OmitFunctions> 85 | > & 86 | // Fields of the options argument of the constructor (First argument) 87 | Partial[0]> & { 88 | // Usual props for all elements 89 | attach?: 90 | | string 91 | | (( 92 | container: Container, 93 | child: Child 94 | ) => (container: Container, child: Child) => void); 95 | attachArray?:string; 96 | onUpdate?: (...args: any[]) => void; 97 | children?: React.ReactNode | React.ReactNodeArray; 98 | ref?: React.Ref; 99 | key?: React.Key; 100 | args?: 101 | | ConstructorParameters 102 | | ConstructorParameters[0]; 103 | } & Events & { [key: string]: any }; // Event listeners (generated manually dirtily for now) // Other props that can be set using a specific setter but that don't exist in the object (see geom.point.coordinates for example) 104 | }; 105 | 106 | /** 107 | * Generic elements based on more complex constructors 108 | */ 109 | type IntrinsicElementsArgsObject = { 110 | [T in keyof ComplexObjectKeys]: Partial< 111 | // Fields of the class that are not functions (Most of the time there isn't any) 112 | OmitFunctions> 113 | > & { 114 | // Usual props for all elements 115 | attach?: 116 | | string 117 | | (( 118 | container: Container, 119 | child: Child 120 | ) => (container: Container, child: Child) => void); 121 | attachArray?:string; 122 | onUpdate?: (...args: any[]) => void; 123 | children?: React.ReactNode | React.ReactNodeArray; 124 | ref?: React.Ref; 125 | key?: React.Key; 126 | // This should be the keys of static methods of Catalogue[T]["object"] 127 | constructFrom?: keyof Catalogue[T]["object"]; 128 | // Fields of the options argument of the constructor (First argument) 129 | // // FIXME disable for now 130 | // args?: 131 | // | ConstructorParameters 132 | // | ConstructorParameters[0]; 133 | args?: any; 134 | } & Events & { [key: string]: any }; // Events listener (generated manually dirtily for now) // Other props that can be set using a specific setter but that don't exist in the object (see geom.point.coordinates for example) 135 | }; 136 | 137 | /** 138 | * Specific ad-hoc elements 139 | */ 140 | type IntrinsicElementsAdHoc = { 141 | // Primitive 142 | olPrimitive: { object: any } & { [properties: string]: any }; 143 | olNew: { object: any; args: any[] } & { [properties: string]: any }; 144 | }; 145 | 146 | type IntrinsicElements = ReactOlFiber.IntrinsicElementsAdHoc & 147 | ReactOlFiber.IntrinsicElementsSimpleObject & 148 | ReactOlFiber.IntrinsicElementsArgsObject; 149 | } 150 | 151 | declare global { 152 | // eslint-disable-next-line @typescript-eslint/no-namespace 153 | namespace JSX { 154 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 155 | interface IntrinsicElements extends ReactOlFiber.IntrinsicElements {} 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /packages/react-openlayers-fiber/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | test: { 7 | globals: true, 8 | environment: 'jsdom', 9 | include: ['**/*.vitest.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], 10 | setupFiles: './src/test/setup.ts', 11 | }, 12 | }) -------------------------------------------------------------------------------- /packages/website/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /packages/website/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | 40 | .env -------------------------------------------------------------------------------- /packages/website/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /packages/website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /packages/website/next.config.js: -------------------------------------------------------------------------------- 1 | const createWithNextra = require('nextra'); 2 | 3 | const withNextra = createWithNextra({ 4 | theme: 'nextra-theme-docs', 5 | themeConfig: './theme.config.js', 6 | unstable_flexsearch: true, 7 | unstable_staticImage: true, 8 | }) 9 | 10 | /** @type {import('next').NextConfig} */ 11 | const nextConfig = withNextra({ 12 | i18n: { 13 | locales: ["en-US"], 14 | defaultLocale: "en-US", 15 | }, 16 | experimental: { 17 | // Prefer loading of ES Modules over CommonJS 18 | // @link {https://nextjs.org/blog/next-11-1#es-modules-support|Blog 11.1.0} 19 | // @link {https://github.com/vercel/next.js/discussions/27876|Discussion} 20 | esmExternals: true, 21 | // Experimental monorepo support 22 | // @link {https://github.com/vercel/next.js/pull/22867|Original PR} 23 | // @link {https://github.com/vercel/next.js/discussions/26420|Discussion} 24 | externalDir: true, 25 | }, 26 | reactStrictMode: true, 27 | redirects: () => { 28 | return [ 29 | { 30 | source: "/docs", 31 | destination: "/docs/getting-started", 32 | statusCode: 302, 33 | }, 34 | { 35 | source: "/examples/openlayers", 36 | destination: "/examples/openlayers/simple", 37 | statusCode: 302, 38 | }, 39 | { 40 | source: "/examples", 41 | destination: "/examples/openlayers/simple", 42 | statusCode: 302, 43 | }, 44 | ] 45 | } 46 | }) 47 | 48 | module.exports = nextConfig 49 | -------------------------------------------------------------------------------- /packages/website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-ol/website", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@react-ol/fiber": "workspace:*", 13 | "@tailwindcss/forms": "0.5.0", 14 | "@turf/turf": "6.5.0", 15 | "next": "12.1.1", 16 | "nextra": "2.0.0-alpha.34", 17 | "nextra-theme-docs": "2.0.0-alpha.34", 18 | "ol": "6.13.0", 19 | "proj4": "2.8.0", 20 | "react": "latest", 21 | "react-dom": "latest", 22 | "react-use": "17.3.2" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "17.0.21", 26 | "@types/react": "17.0.40", 27 | "autoprefixer": "10.4.2", 28 | "eslint": "8.11.0", 29 | "eslint-config-next": "12.1.0", 30 | "postcss": "8.4.8", 31 | "tailwindcss": "3.0.23", 32 | "typescript": "4.6.2", 33 | "webpack": "5.70.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/website/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import 'nextra-theme-docs/style.css' 3 | 4 | import type { AppProps } from 'next/app' 5 | 6 | function MyApp({ Component, pageProps }: AppProps) { 7 | return 8 | } 9 | 10 | export default MyApp 11 | -------------------------------------------------------------------------------- /packages/website/pages/_middleware.tsx: -------------------------------------------------------------------------------- 1 | 2 | // @ts-ignore 3 | import { locales } from "nextra/locales"; 4 | 5 | export const middleware = locales; -------------------------------------------------------------------------------- /packages/website/pages/docs/components.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Components 2 | 3 | ## OpenLayers-derived components 4 | 5 | All OpenLayers classes are available as React Component through the automatic API. 6 | 7 | The naming scheme is using the camel-cased fully qualified names of the classes. 8 | 9 | Examples: 10 | 11 | | OpenLayers Class | React OpenLayers Fiber component | 12 | | -------------------------------------------------------------------------------------------------- | -------------------------------- | 13 | | [`ol/View`](https://openlayers.org/en/latest/apidoc/module-ol_View-View.html) | `` | 14 | | [`ol/geom/Polygon`](https://openlayers.org/en/latest/apidoc/module-ol_geom_Polygon-Polygon.html) | `` | 15 | | [`ol/layer/Heatmap`](https://openlayers.org/en/latest/apidoc/module-ol_layer_Heatmap-Heatmap.html) | `` | 16 | 17 | ```tsx 18 | import { Map } from "@react-ol/fiber"; 19 | 20 | export const Example = () => ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | ``` 29 | 30 | ## The `Map` component 31 | 32 | The `` component is the main component, it allows to transition from `react-dom` to the `@react-ol/fiber` rendering realm. 33 | 34 | The `` component takes props for `` element that wraps the map, **and** for the `` component that is the actual OpenLayers `ol.Map` object. 35 | 36 | ```tsx 37 | import { Map } from "@react-ol/fiber"; 38 | 39 | export const Example = () => ( 40 | 41 | 42 | 43 | 44 | 45 | 46 | ); 47 | ``` 48 | 49 | ## The `olPrimitive` component 50 | 51 | If you want to use your own already instanced objects, you can use the `` wrapper and set a custom attach: 52 | 53 | ```tsx 54 | import React from "react"; 55 | import "ol/ol.css"; 56 | import { Map } from "@react-ol/fiber"; 57 | import { OSM } from "ol/source"; 58 | 59 | export const Example1 = () => { 60 | return ( 61 | 62 | 63 | 64 | 65 | 66 | 67 | ); 68 | }; 69 | 70 | export const source = new OSM(); 71 | export const Example2 = () => { 72 | return ( 73 | 74 | 75 | 76 | 77 | 78 | 79 | ); 80 | }; 81 | ``` 82 | 83 | Here, Example1 and Example2 work the same way. 84 | -------------------------------------------------------------------------------- /packages/website/pages/docs/getting-started.en-US.mdx: -------------------------------------------------------------------------------- 1 | import Callout from "nextra-theme-docs/callout"; 2 | import Bleed from "nextra-theme-docs/bleed"; 3 | 4 | # Getting Started 5 | 6 | ## Install 7 | 8 | Install with your favorite package manager: 9 | 10 | ```sh 11 | yarn add @react-ol/fiber 12 | ``` 13 | 14 | ```sh 15 | npm install @react-ol/fiber 16 | ``` 17 | 18 | ```sh 19 | pnpm add @react-ol/fiber 20 | ``` 21 | 22 | ## Usage 23 | 24 | Use the `Map` component as the root. Children of the `Map` component should be OpenLayers objects that will be rendered on the map. 25 | 26 | ```tsx 27 | import { Map } from "@react-ol/fiber"; 28 | 29 | function MyMap() { 30 | return ( 31 | 32 | 33 | 34 | 35 | 36 | 37 | ); 38 | } 39 | ``` 40 | 41 | ## Examples 42 | 43 | Examples are visible on the [example section](/examples/openlayers/simple) of this website. 44 | 45 | Most of the examples are simply ported from [openlayers examples section](https://openlayers.org/en/latest/examples/). 46 | -------------------------------------------------------------------------------- /packages/website/pages/docs/hooks.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Hooks 2 | 3 | ## The `useMap` hook 4 | 5 | The `useMap` hook allows to access the `Map` instance. Remember that this can work only inside a component that is child of a `` component. 6 | 7 | ```tsx 8 | import React from "react"; 9 | import { Map, useMap } from "@react-ol/fiber"; 10 | import "ol/ol.css"; 11 | 12 | export const Inner = () => { 13 | const map = useMap(); 14 | function centerOnFeatures(extent) { 15 | const view = map.getView(); 16 | view.fit(extent); 17 | } 18 | return ( 19 | 20 | centerOnFeatures(e.target.getExtent())}> 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | export const Parent = () => { 30 | // WARNING: you can't use useOL() here 31 | return ( 32 | 33 | 34 | 35 | ); 36 | }; 37 | ``` 38 | -------------------------------------------------------------------------------- /packages/website/pages/docs/imperative-fallback.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Imperative Fallback 2 | 3 | The [`ref` prop](/docs/props#ref-prop) allows to access the underlying OpenLayers object imperatively. 4 | 5 | For example, to access the `ol.View` OpenLayers object instance, you can use the `` prop: 6 | 7 | ```tsx 8 | export const AccessibleMap = () => { 9 | const viewRef = useRef(); 10 | return ( 11 | 12 | { 14 | viewRef.current?.setZoom(viewRef.current.getZoom() + 1); 15 | }} 16 | > 17 | Zoom in 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | ``` 29 | -------------------------------------------------------------------------------- /packages/website/pages/docs/meta.en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "getting-started": "Getting Started", 3 | "components": "Components", 4 | "props": "Props", 5 | "hooks": "Hooks", 6 | "imperative-fallback": "Imperative Fallback" 7 | } 8 | -------------------------------------------------------------------------------- /packages/website/pages/docs/props.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Props 2 | 3 | The openlayers components take three different kind of props: 4 | 5 | - classic props 6 | - props prefixed with `initial` 7 | - Special props: 8 | - `args` to control OpenLayers constructor args 9 | - `attach` to customize attachment of components to their parents 10 | - `ref` to access the OpenLayers object imperatively 11 | 12 | ## Classic props 13 | 14 | Classic props behave like any prop in React but the OpenLayer object must have a setter for the prop to work fine (or being settable by the generic setter `set(key, value)`. 15 | 16 | ```tsx 17 | 18 | ``` 19 | 20 | This will become `const myView = new ol.View()`. And then each re-render of this component will call `myView.setCenter([0, 6000000])`. 21 | 22 | ## `initial`-prefixed props 23 | 24 | The `initial` prefix applies to the same OpenLayers properties as classic props. 25 | It gives you the ability to set a default value to a prop so it behaves as a uncontrolled input. Here is an example: 26 | 27 | ```tsx 28 | import { Map } from "@react-ol/fibe"; 29 | 30 | function MyMap() { 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | 38 | ); 39 | } 40 | ``` 41 | 42 | By using the `initial` prefix here the map won't reset the position and the zoom each time the component `MyMap` is rerendered. 43 | 44 | ## `args` prop 45 | 46 | You must use the `args` prop for properties of OpenLayers objects who don't have a specific setter or isn't updatable through the generic setter. Here is an example: 47 | 48 | ```tsx 49 | function MyMap() { 50 | return ( 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | ); 77 | } 78 | ``` 79 | 80 | The args's properties are directly passed in the openlayers object's constructor. 81 | If a property value changed during you component lifecycle it trigger a special behavior that delete the openlayers object and create a new one with the updated `args` property value. 82 | 83 | ## `attach` prop 84 | 85 | React OpenLayers Fiber usually infers how to relate each component to its parent. 86 | 87 | For example it knows that an `olSource` react component inside a `olLayer` react component will mean that the `ol.Source` OpenLayer Object will be attached to the `ol.Layer` OpenLayer Object's `source` property, using `ol.layer.setSource(...)`, or `ol.layer.set("source",...)`. 88 | 89 | You can override this behavior by using the `attach` prop. If you use `attach="foobar"` then React OpenLayers will attach to the parent using `ol.layer.setFoobar(...)`, or `ol.layer.set("foobar",...)`. 90 | 91 | ## `ref` prop 92 | 93 | The `ref` prop allows to access the underlying OpenLayers object imperatively. 94 | 95 | For example, to access the `ol.View` OpenLayers object instance, you can use the `` prop: 96 | 97 | ```tsx 98 | export const AccessibleMap = () => { 99 | const viewRef = useRef(); 100 | return ( 101 | 102 | { 104 | viewRef.current?.setZoom(viewRef.current.getZoom() + 1); 105 | }} 106 | > 107 | Zoom in 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | ); 117 | }; 118 | ``` 119 | 120 | ## Events props 121 | 122 | All events described in the OpenLayers documentation are capitalized and prefixed with "on". The list of event supported for typing can be found in [events.ts](https://github.com/crubier/react-openlayers-fiber/blob/master/packages/react-openlayers-fiber/src/events.ts) but other event types might be supported. 123 | 124 | ```tsx 125 | export const Component = () => { 126 | // This will set the 'select' event 127 | return console.log(e)} />; 128 | 129 | // This will set the 'change' event 130 | return console.log(e)} />; 131 | 132 | // It also works on the map component! 133 | return console.log(e.coordinate)} />; 134 | }; 135 | ``` 136 | 137 | ## `onUpdate` prop 138 | 139 | An additional event props, `onUpdate`, is added by `@react-ol/fiber` to all components, and is called every time an openlayers object is updated when rendering. 140 | -------------------------------------------------------------------------------- /packages/website/pages/examples/advanced/declaratively-move-view.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Declaratively move view 2 | 3 | In this example we only provide props for the view, so it should load with default values 4 | and go a bit north every second, forcing the view to recenter every second 5 | 6 | import React, { useState } from "react"; 7 | import "ol/ol.css"; 8 | import { Map } from "@react-ol/fiber"; 9 | import { useInterval } from "react-use"; 10 | 11 | export const ExampleDeclarativelyMoveView = () => { 12 | const [latitude, setLatitude] = useState(6000000); 13 | useInterval(() => { 14 | setLatitude(latitude + 10000); 15 | }, 1000); 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | 27 | 28 | ```tsx 29 | import React, { useState } from "react"; 30 | import "ol/ol.css"; 31 | import { Map } from "@react-ol/fiber"; 32 | import { useInterval } from "react-use"; 33 | 34 | export const ExampleDeclarativelyMoveView = () => { 35 | const [latitude, setLatitude] = useState(6000000); 36 | useInterval(() => { 37 | setLatitude(latitude + 10000); 38 | }, 1000); 39 | return ( 40 | 41 | 42 | 43 | 44 | 45 | 46 | ); 47 | }; 48 | ``` 49 | 50 | In this second example we provide props and initial props for the view, so it should behave like the Basic Props example values. 51 | It loads with the initial values and immediatly the position get changed with the position from props 52 | 53 | export const ExampleInitialProps = () => { 54 | const [latitude, setLatitude] = useState(6000000); 55 | useInterval(() => { 56 | setLatitude(latitude + 10000); 57 | }, 1000); 58 | return ( 59 | 60 | 65 | 66 | 67 | 68 | 69 | ); 70 | }; 71 | 72 | 73 | 74 | ```tsx 75 | export const ExampleInitialProps = () => { 76 | const [latitude, setLatitude] = useState(6000000); 77 | useInterval(() => { 78 | setLatitude(latitude + 10000); 79 | }, 1000); 80 | return ( 81 | 82 | 87 | 88 | 89 | 90 | 91 | ); 92 | }; 93 | ``` 94 | -------------------------------------------------------------------------------- /packages/website/pages/examples/advanced/dynamic-points.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Dynamic points 2 | 3 | In this example we declaratively update points at 30FPS. 4 | 5 | import React, { useState } from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | import { useInterval } from "react-use"; 9 | import { Fill, Stroke, Style, RegularShape } from "ol/style"; 10 | 11 | export const stroke = new Stroke({ color: "black", width: 2 }); 12 | export const fill = new Fill({ color: "red" }); 13 | export const pointStyle = new Style({ 14 | image: new RegularShape({ 15 | fill, 16 | stroke, 17 | points: 4, 18 | radius: 10, 19 | angle: Math.PI / 4, 20 | }), 21 | }); 22 | 23 | export const ExampleDynamicPoints = () => { 24 | const [location, setLocation] = useState([0, 6000000]); 25 | useInterval(() => { 26 | setLocation([100000 * Math.random(), 6000000 + 100000 * Math.random()]); 27 | }, 1000 / 30); 28 | return ( 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | }; 44 | 45 | 46 | 47 | ```tsx 48 | import React, { useState } from "react"; 49 | import "ol/ol.css"; 50 | import { Map } from "@react-ol/fiber"; 51 | import { useInterval } from "react-use"; 52 | import { Fill, Stroke, Style, RegularShape } from "ol/style"; 53 | 54 | export const stroke = new Stroke({ color: "black", width: 2 }); 55 | export const fill = new Fill({ color: "red" }); 56 | export const pointStyle = new Style({ 57 | image: new RegularShape({ 58 | fill, 59 | stroke, 60 | points: 4, 61 | radius: 10, 62 | angle: Math.PI / 4, 63 | }), 64 | }); 65 | 66 | export const ExampleDynamicPoints = () => { 67 | const [location, setLocation] = useState([0, 6000000]); 68 | useInterval(() => { 69 | setLocation([100000 * Math.random(), 6000000 + 100000 * Math.random()]); 70 | }, 1000 / 30); 71 | return ( 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | ); 86 | }; 87 | ``` 88 | -------------------------------------------------------------------------------- /packages/website/pages/examples/advanced/dynamic-styles.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Dynamic styles 2 | 3 | In this example we declaratively update points at 30FPS with a custom style. 4 | 5 | import React, { useState } from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | import { useInterval } from "react-use"; 9 | import { Fill, Stroke } from "ol/style"; 10 | 11 | export const stroke = new Stroke({ color: "black", width: 2 }); 12 | export const fill = new Fill({ color: "red" }); 13 | 14 | export const ExampleDynamicStyle = () => { 15 | const [location, setLocation] = useState([0, 6000000]); 16 | useInterval(() => { 17 | setLocation([100000 * Math.random(), 6000000 + 100000 * Math.random()]); 18 | }, 1000 / 30); 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ); 46 | }; 47 | 48 | 49 | 50 | ```tsx 51 | import React, { useState } from "react"; 52 | import "ol/ol.css"; 53 | import { Map } from "@react-ol/fiber"; 54 | import { useInterval } from "react-use"; 55 | import { Fill, Stroke } from "ol/style"; 56 | 57 | export const stroke = new Stroke({ color: "black", width: 2 }); 58 | export const fill = new Fill({ color: "red" }); 59 | 60 | export const ExampleDynamicStyle = () => { 61 | const [location, setLocation] = useState([0, 6000000]); 62 | useInterval(() => { 63 | setLocation([100000 * Math.random(), 6000000 + 100000 * Math.random()]); 64 | }, 1000 / 30); 65 | return ( 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | ); 92 | }; 93 | ``` 94 | -------------------------------------------------------------------------------- /packages/website/pages/examples/advanced/kitchen-sink.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Kitchen Sink 2 | 3 | This is a kitchen sink example showing several features combined. Features showed here: 4 | 5 | - Change base map 6 | - View the coordinates of the view center as you pan 7 | - Draw polygons 8 | - Block drawing polygons when the base map is the second element of the list 9 | 10 | import React, { useEffect, useState } from "react"; 11 | import { Fill, RegularShape, Stroke, Style } from "ol/style"; 12 | import { Draw } from "ol/interaction"; 13 | import { debounce } from "lodash/fp"; 14 | import GeometryType from "ol/geom/GeometryType"; 15 | import VectorSource from "ol/source/Vector"; 16 | import { Geometry } from "ol/geom"; 17 | import "ol/ol.css"; 18 | import { Map } from "@react-ol/fiber"; 19 | 20 | export const bingstyles = [ 21 | "Road", 22 | "RoadOnDemand", 23 | "Aerial", 24 | "AerialWithLabelsOnDemand", 25 | "CanvasDark", 26 | "OrdnanceSurvey", 27 | ]; 28 | export const stroke = new Stroke({ color: "black", width: 2 }); 29 | export const fill = new Fill({ color: "red" }); 30 | export const pointStyle = new Style({ 31 | image: new RegularShape({ 32 | fill, 33 | stroke, 34 | points: 4, 35 | radius: 10, 36 | angle: Math.PI / 4, 37 | }), 38 | }); 39 | export const polygonStyle = new Style({ 40 | stroke: new Stroke({ 41 | color: "red", 42 | width: 3, 43 | }), 44 | fill: new Fill({ 45 | color: "rgba(0, 0, 255, 0.1)", 46 | }), 47 | }); 48 | 49 | export const ExampleKitchenSink = () => { 50 | const [currentStyle, setCurrentStyle] = useState(bingstyles[0]); 51 | const [center, setCenter] = useState([0, 0]); 52 | const [vectorSource, setVectorSource] = useState(); 53 | const [drawInteraction, setDrawInteraction] = useState(); 54 | useEffect(() => { 55 | if (drawInteraction) { 56 | if (currentStyle === bingstyles[1]) { 57 | drawInteraction.setActive(false); 58 | } else { 59 | drawInteraction.setActive(true); 60 | } 61 | } 62 | }, [currentStyle, drawInteraction]); 63 | return ( 64 | <> 65 | setCurrentStyle(e.target.value)} 69 | > 70 | {bingstyles.map((style) => ( 71 | 72 | {style} 73 | 74 | ))} 75 | 76 | Center: {center.join(", ")} 77 | 78 | { 80 | setCenter(e.target.getCenter()); 81 | return true; 82 | })} 83 | initialCenter={[-6655.5402445057125, 6709968.258934638]} 84 | initialZoom={13} 85 | /> 86 | 87 | 88 | 89 | 90 | {bingstyles.map((style) => ( 91 | 96 | 100 | 101 | ))} 102 | 103 | 104 | 105 | 106 | 107 | 108 | 120 | 121 | 122 | 123 | {vectorSource ? ( 124 | 129 | ) : null} 130 | 131 | > 132 | ); 133 | }; 134 | 135 | 136 | 137 | ```tsx 138 | import React, { useEffect, useState } from "react"; 139 | import { Fill, RegularShape, Stroke, Style } from "ol/style"; 140 | import { Draw } from "ol/interaction"; 141 | import { debounce } from "lodash/fp"; 142 | import GeometryType from "ol/geom/GeometryType"; 143 | import VectorSource from "ol/source/Vector"; 144 | import { Geometry } from "ol/geom"; 145 | import "ol/ol.css"; 146 | import { Map } from "@react-ol/fiber"; 147 | 148 | export const bingstyles = [ 149 | "Road", 150 | "RoadOnDemand", 151 | "Aerial", 152 | "AerialWithLabelsOnDemand", 153 | "CanvasDark", 154 | "OrdnanceSurvey", 155 | ]; 156 | export const stroke = new Stroke({ color: "black", width: 2 }); 157 | export const fill = new Fill({ color: "red" }); 158 | export const pointStyle = new Style({ 159 | image: new RegularShape({ 160 | fill, 161 | stroke, 162 | points: 4, 163 | radius: 10, 164 | angle: Math.PI / 4, 165 | }), 166 | }); 167 | export const polygonStyle = new Style({ 168 | stroke: new Stroke({ 169 | color: "red", 170 | width: 3, 171 | }), 172 | fill: new Fill({ 173 | color: "rgba(0, 0, 255, 0.1)", 174 | }), 175 | }); 176 | 177 | export const ExampleKitchenSink = () => { 178 | const [currentStyle, setCurrentStyle] = useState(bingstyles[0]); 179 | const [center, setCenter] = useState([0, 0]); 180 | const [vectorSource, setVectorSource] = useState(); 181 | const [drawInteraction, setDrawInteraction] = useState(); 182 | useEffect(() => { 183 | if (drawInteraction) { 184 | if (currentStyle === bingstyles[1]) { 185 | drawInteraction.setActive(false); 186 | } else { 187 | drawInteraction.setActive(true); 188 | } 189 | } 190 | }, [currentStyle, drawInteraction]); 191 | return ( 192 | <> 193 | setCurrentStyle(e.target.value)} 196 | > 197 | {bingstyles.map((style) => ( 198 | 199 | {style} 200 | 201 | ))} 202 | 203 | Center: {center.join(", ")} 204 | 205 | { 207 | setCenter(e.target.getCenter()); 208 | return true; 209 | })} 210 | initialCenter={[-6655.5402445057125, 6709968.258934638]} 211 | initialZoom={13} 212 | /> 213 | 214 | 215 | 216 | 217 | {bingstyles.map((style) => ( 218 | 223 | 227 | 228 | ))} 229 | 230 | 231 | 232 | 233 | 234 | 235 | 247 | 248 | 249 | 250 | {vectorSource ? ( 251 | 256 | ) : null} 257 | 258 | > 259 | ); 260 | }; 261 | ``` 262 | -------------------------------------------------------------------------------- /packages/website/pages/examples/advanced/meta.en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "declaratively-move-view": "Declaratively move view", 3 | "dynamic-styles": "Dynamic styles", 4 | "dynamic-points": "Dynamic points", 5 | "retina": "Retina", 6 | "performance": "Performance", 7 | "kitchen-sink": "Kitchen Sink" 8 | } 9 | -------------------------------------------------------------------------------- /packages/website/pages/examples/advanced/performance.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Performance 2 | 3 | This example demonstrates updating 1000 points on the map at up to 60FPS, every time the map is rendered 4 | 5 | import React, { useRef, useCallback } from "react"; 6 | import "ol/ol.css"; 7 | import { Fill, Stroke, RegularShape, Style } from "ol/style"; 8 | import { Map as OlMap } from "ol"; 9 | import { getVectorContext } from "ol/render"; 10 | import { Point } from "ol/geom"; 11 | import RenderEvent from "ol/render/Event"; 12 | import { Map } from "@react-ol/fiber"; 13 | 14 | export const stroke = new Stroke({ color: "black", width: 2 }); 15 | export const fill = new Fill({ color: "red" }); 16 | export const point = new Point([0, 0]); 17 | export const styles = []; 18 | 19 | export const ExamplePerformance = () => { 20 | const mapRef = useRef(null); 21 | const onPostrender = useCallback( 22 | (event) => { 23 | const vectorContext = getVectorContext(event); 24 | for (let i = 0; i < 1000; i++) { 25 | const coordinates = [ 26 | 1000000 * Math.random(), // + i * 10000, 27 | 6000000 + 1000000 * Math.random(), // + i * 10000 28 | ]; 29 | const radius = Math.floor(Math.random() * 20); 30 | if (!styles[radius]) { 31 | styles[radius] = new Style({ 32 | image: new RegularShape({ 33 | fill, 34 | stroke, 35 | radius, 36 | points: 4, 37 | angle: Math.PI / 4, 38 | }), 39 | }); 40 | } 41 | const style = styles[radius]; 42 | point.setCoordinates(coordinates); 43 | vectorContext.setStyle(style); 44 | vectorContext.drawGeometry(point); 45 | } 46 | mapRef.current?.render(); 47 | return true; 48 | }, 49 | [mapRef] 50 | ); 51 | return ( 52 | 53 | 54 | 55 | 56 | 57 | 58 | ); 59 | }; 60 | 61 | 62 | 63 | ```tsx 64 | import React, { useRef, useCallback } from "react"; 65 | import "ol/ol.css"; 66 | import { Fill, Stroke, RegularShape, Style } from "ol/style"; 67 | import { Map as OlMap } from "ol"; 68 | import { getVectorContext } from "ol/render"; 69 | import { Point } from "ol/geom"; 70 | import RenderEvent from "ol/render/Event"; 71 | import { Map } from "@react-ol/fiber"; 72 | 73 | export const stroke = new Stroke({ color: "black", width: 2 }); 74 | export const fill = new Fill({ color: "red" }); 75 | export const point = new Point([0, 0]); 76 | export const styles = []; 77 | 78 | export const ExamplePerformance = () => { 79 | const mapRef = useRef(null); 80 | const onPostrender = useCallback( 81 | (event) => { 82 | const vectorContext = getVectorContext(event); 83 | for (let i = 0; i < 1000; i++) { 84 | const coordinates = [ 85 | 1000000 * Math.random(), // + i * 10000, 86 | 6000000 + 1000000 * Math.random(), // + i * 10000 87 | ]; 88 | const radius = Math.floor(Math.random() * 20); 89 | if (!styles[radius]) { 90 | styles[radius] = new Style({ 91 | image: new RegularShape({ 92 | fill, 93 | stroke, 94 | radius, 95 | points: 4, 96 | angle: Math.PI / 4, 97 | }), 98 | }); 99 | } 100 | const style = styles[radius]; 101 | point.setCoordinates(coordinates); 102 | vectorContext.setStyle(style); 103 | vectorContext.drawGeometry(point); 104 | } 105 | mapRef.current?.render(); 106 | return true; 107 | }, 108 | [mapRef] 109 | ); 110 | return ( 111 | 112 | 113 | 114 | 115 | 116 | 117 | ); 118 | }; 119 | ``` 120 | -------------------------------------------------------------------------------- /packages/website/pages/examples/advanced/retina.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Retina 2 | 3 | This example shows how to use display maps that looks good on retina screens 4 | 5 | import React from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | 9 | export const ExampleRetina = () => ( 10 | 11 | 12 | 13 | 19 | 20 | 21 | ); 22 | 23 | 24 | 25 | ```tsx 26 | import React from "react"; 27 | import "ol/ol.css"; 28 | import { Map } from "@react-ol/fiber"; 29 | 30 | export const ExampleRetina = () => ( 31 | 32 | 33 | 34 | 40 | 41 | 42 | ); 43 | ``` 44 | -------------------------------------------------------------------------------- /packages/website/pages/examples/meta.en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "openlayers": "OpenLayers examples", 3 | "advanced": "Advanced examples" 4 | } 5 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/accessible.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Accessible Map 2 | 3 | See https://openlayers.org/en/latest/examples/accessible.html 4 | 5 | import React, { useRef } from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | 9 | export const ExampleAccessible = () => { 10 | const viewRef = useRef(); 11 | return ( 12 | <> 13 | { 16 | viewRef.current?.setZoom(viewRef.current.getZoom() - 1); 17 | }} 18 | > 19 | Zoom out 20 | 21 | { 24 | viewRef.current?.setZoom(viewRef.current.getZoom() + 1); 25 | }} 26 | > 27 | Zoom in 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | > 36 | ); 37 | }; 38 | 39 | 40 | 41 | ```tsx 42 | import React, { useRef } from "react"; 43 | import "ol/ol.css"; 44 | import { Map } from "@react-ol/fiber"; 45 | 46 | export const ExampleAccessible = () => { 47 | const viewRef = useRef(); 48 | return ( 49 | <> 50 | { 52 | viewRef.current?.setZoom(viewRef.current.getZoom() - 1); 53 | }} 54 | > 55 | Zoom out 56 | 57 | { 59 | viewRef.current?.setZoom(viewRef.current.getZoom() + 1); 60 | }} 61 | > 62 | Zoom in 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | > 71 | ); 72 | }; 73 | ``` 74 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/bing-maps.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Bing Maps 2 | 3 | See https://openlayers.org/en/latest/examples/bing-maps.html 4 | 5 | import React, { useState } from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | 9 | export const styles = [ 10 | "RoadOnDemand", 11 | "Aerial", 12 | "AerialWithLabelsOnDemand", 13 | "CanvasDark", 14 | "OrdnanceSurvey", 15 | ]; 16 | 17 | export const ExampleBingMaps = () => { 18 | const [currentStyle, setCurrentStyle] = useState(styles[0]); 19 | return ( 20 | <> 21 | setCurrentStyle(e.target.value)} 25 | > 26 | {styles.map((style) => ( 27 | 28 | {style} 29 | 30 | ))} 31 | 32 | 33 | 37 | {styles.map((style) => ( 38 | 43 | 47 | 48 | ))} 49 | 50 | > 51 | ); 52 | }; 53 | 54 | 55 | 56 | ```tsx 57 | import React, { useState } from "react"; 58 | import "ol/ol.css"; 59 | import { Map } from "@react-ol/fiber"; 60 | 61 | export const styles = [ 62 | "RoadOnDemand", 63 | "Aerial", 64 | "AerialWithLabelsOnDemand", 65 | "CanvasDark", 66 | "OrdnanceSurvey", 67 | ]; 68 | 69 | export const ExampleBingMaps = () => { 70 | const [currentStyle, setCurrentStyle] = useState(styles[0]); 71 | return ( 72 | <> 73 | setCurrentStyle(e.target.value)} 76 | > 77 | {styles.map((style) => ( 78 | 79 | {style} 80 | 81 | ))} 82 | 83 | 84 | 88 | {styles.map((style) => ( 89 | 94 | 98 | 99 | ))} 100 | 101 | > 102 | ); 103 | }; 104 | ``` 105 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/cluster.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Clustered Features 2 | 3 | See https://openlayers.org/en/latest/examples/cluster.html 4 | 5 | import React, { useState, memo } from "react"; 6 | import "ol/ol.css"; 7 | import { Style, Circle as CircleStyle, Fill, Stroke, Text } from "ol/style"; 8 | import { Map } from "@react-ol/fiber"; 9 | 10 | export const coordinates = (() => { 11 | const count = 20000; 12 | const e = 4500000; 13 | const coordinates = new Array(count); 14 | for (let i = 0; i < count; i++) { 15 | coordinates[i] = [2 * e * Math.random() - e, 2 * e * Math.random() - e]; 16 | } 17 | return coordinates; 18 | })(); 19 | 20 | export const styleCache = []; 21 | 22 | export const PointsAtCoordinates = memo(({ coordinates }) => { 23 | // It is important to memoize this, to avoid iterating over all the coordinates 24 | // on each react render if the array is not changed. 25 | return coordinates.map((coordinate) => ( 26 | 27 | 28 | 29 | )); 30 | }); 31 | 32 | export const ExampleCluster = () => { 33 | const [distance, setDistance] = useState(40); 34 | const [minDistance, setMinDistance] = useState(20); 35 | return ( 36 | <> 37 | 38 | Cluster distance 39 | setDistance(parseInt(e.target.value, 10))} 47 | /> 48 | Minimum distance 49 | setMinDistance(parseInt(e.target.value, 10))} 57 | /> 58 | 59 | 60 | 61 | 62 | 63 | 64 | { 66 | const size = feature.get("features").length; 67 | let style = styleCache[size]; 68 | if (!style) { 69 | style = new Style({ 70 | image: new CircleStyle({ 71 | radius: 10, 72 | stroke: new Stroke({ 73 | color: "#fff", 74 | }), 75 | fill: new Fill({ 76 | color: "#3399CC", 77 | }), 78 | }), 79 | text: new Text({ 80 | text: size.toString(), 81 | fill: new Fill({ 82 | color: "#fff", 83 | }), 84 | }), 85 | }); 86 | styleCache[size] = style; 87 | } 88 | return style; 89 | }} 90 | > 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | > 99 | ); 100 | }; 101 | 102 | 103 | 104 | ```tsx 105 | import React, { useState, memo } from "react"; 106 | import "ol/ol.css"; 107 | import { Style, Circle as CircleStyle, Fill, Stroke, Text } from "ol/style"; 108 | import { Map } from "@react-ol/fiber"; 109 | 110 | export const coordinates = (() => { 111 | const count = 20000; 112 | const e = 4500000; 113 | const coordinates = new Array(count); 114 | for (let i = 0; i < count; i++) { 115 | coordinates[i] = [2 * e * Math.random() - e, 2 * e * Math.random() - e]; 116 | } 117 | return coordinates; 118 | })(); 119 | 120 | export const styleCache = []; 121 | 122 | export const PointsAtCoordinates = memo(({ coordinates }) => { 123 | // It is important to memoize this, to avoid iterating over all the coordinates 124 | // on each react render if the array is not changed. 125 | return coordinates.map((coordinate) => ( 126 | 127 | 128 | 129 | )); 130 | }); 131 | 132 | export const ExampleCluster = () => { 133 | const [distance, setDistance] = useState(40); 134 | const [minDistance, setMinDistance] = useState(20); 135 | return ( 136 | <> 137 | 138 | Cluster distance 139 | setDistance(parseInt(e.target.value, 10))} 147 | /> 148 | Minimum distance 149 | setMinDistance(parseInt(e.target.value, 10))} 157 | /> 158 | 159 | 160 | 161 | 162 | 163 | 164 | { 166 | const size = feature.get("features").length; 167 | let style = styleCache[size]; 168 | if (!style) { 169 | style = new Style({ 170 | image: new CircleStyle({ 171 | radius: 10, 172 | stroke: new Stroke({ 173 | color: "#fff", 174 | }), 175 | fill: new Fill({ 176 | color: "#3399CC", 177 | }), 178 | }), 179 | text: new Text({ 180 | text: size.toString(), 181 | fill: new Fill({ 182 | color: "#fff", 183 | }), 184 | }), 185 | }); 186 | styleCache[size] = style; 187 | } 188 | return style; 189 | }} 190 | > 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | > 199 | ); 200 | }; 201 | ``` 202 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/draw-and-modify-features.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Draw and Modify Features 2 | 3 | See https://openlayers.org/en/latest/examples/draw-and-modify-features.html 4 | 5 | import React, { useState } from "react"; 6 | import "ol/ol.css"; 7 | import { Stroke, Fill } from "ol/style"; 8 | import VectorSource from "ol/source/Vector"; 9 | import GeometryType from "ol/geom/GeometryType"; 10 | import { Geometry } from "ol/geom"; 11 | import { Map } from "@react-ol/fiber"; 12 | 13 | export const fill = new Fill({ 14 | color: "rgba(255, 255, 255, 0.2)", 15 | }); 16 | export const stroke = new Stroke({ 17 | color: "#ffcc33", 18 | width: 2, 19 | }); 20 | export const circleFill = new Fill({ 21 | color: "#ffcc33", 22 | }); 23 | 24 | export const ExampleDrawAndModifyFeatures = () => { 25 | const [geometryType, setGeometryType] = useState(GeometryType.POINT); 26 | const [vectorSource, setVectorSource] = useState(); 27 | return ( 28 | <> 29 | 30 | Geometry type: 31 | setGeometryType(e.target.value)} 36 | > 37 | Point 38 | LineString 39 | Polygon 40 | Circle 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | {vectorSource ? ( 58 | <> 59 | 60 | 67 | 71 | > 72 | ) : null} 73 | 74 | > 75 | ); 76 | }; 77 | 78 | 79 | 80 | ```tsx 81 | import React, { useState } from "react"; 82 | import "ol/ol.css"; 83 | import { Stroke, Fill } from "ol/style"; 84 | import VectorSource from "ol/source/Vector"; 85 | import GeometryType from "ol/geom/GeometryType"; 86 | import { Geometry } from "ol/geom"; 87 | import { Map } from "@react-ol/fiber"; 88 | 89 | export const fill = new Fill({ 90 | color: "rgba(255, 255, 255, 0.2)", 91 | }); 92 | export const stroke = new Stroke({ 93 | color: "#ffcc33", 94 | width: 2, 95 | }); 96 | export const circleFill = new Fill({ 97 | color: "#ffcc33", 98 | }); 99 | 100 | export const ExampleDrawAndModifyFeatures = () => { 101 | const [geometryType, setGeometryType] = useState(GeometryType.POINT); 102 | const [vectorSource, setVectorSource] = useState(); 103 | return ( 104 | <> 105 | 106 | Geometry type 107 | setGeometryType(e.target.value)} 111 | > 112 | Point 113 | LineString 114 | Polygon 115 | Circle 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 130 | 131 | 132 | {vectorSource ? ( 133 | <> 134 | 135 | 142 | 146 | > 147 | ) : null} 148 | 149 | > 150 | ); 151 | }; 152 | ``` 153 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/draw-shapes.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Draw Shapes 2 | 3 | See https://openlayers.org/en/latest/examples/draw-shapes.html 4 | 5 | import React, { useState } from "react"; 6 | import "ol/ol.css"; 7 | import { 8 | createBox, 9 | createRegularPolygon, 10 | GeometryFunction, 11 | } from "ol/interaction/Draw"; 12 | import OlSourceVector from "ol/source/Vector"; 13 | import Polygon from "ol/geom/Polygon"; 14 | import GeometryType from "ol/geom/GeometryType"; 15 | import { Geometry } from "ol/geom"; 16 | import { Map } from "@react-ol/fiber"; 17 | 18 | export function useGeometryFunction(shapeType) { 19 | if (["None", "Circle"].includes(shapeType)) { 20 | return; 21 | } 22 | if (shapeType === "Square") { 23 | return createRegularPolygon(4); 24 | } 25 | if (shapeType === "Box") { 26 | return createBox(); 27 | } 28 | if (shapeType === "Star") { 29 | return (coordinates, geometry) => { 30 | const center = coordinates[0]; 31 | const last = coordinates[1]; 32 | const dx = center[0] - last[0]; 33 | const dy = center[1] - last[1]; 34 | const radius = Math.sqrt(dx * dx + dy * dy); 35 | const rotation = Math.atan2(dy, dx); 36 | const newCoordinates = []; 37 | const numPoints = 12; 38 | for (let i = 0; i < numPoints; i += 1) { 39 | const angle = rotation + (i * 2 * Math.PI) / numPoints; 40 | const fraction = i % 2 === 0 ? 1 : 0.5; 41 | const offsetX = radius * fraction * Math.cos(angle); 42 | const offsetY = radius * fraction * Math.sin(angle); 43 | newCoordinates.push([center[0] + offsetX, center[1] + offsetY]); 44 | } 45 | newCoordinates.push(newCoordinates[0].slice()); 46 | if (!geometry) { 47 | geometry = new Polygon([newCoordinates]); 48 | } else { 49 | geometry.setCoordinates([newCoordinates]); 50 | } 51 | return geometry; 52 | }; 53 | } 54 | } 55 | 56 | export const ExampleDrawShapes = () => { 57 | const [shapeType, setShapeType] = useState("Circle"); 58 | const geometryFunction = useGeometryFunction(shapeType); 59 | const [vectorSource, setVectorSource] = useState(); 60 | return ( 61 | <> 62 | 63 | Geometry type: 64 | setShapeType(e.target.value)} 69 | > 70 | Circle 71 | Square 72 | Box 73 | Star 74 | None 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | {vectorSource && shapeType !== "None" ? ( 86 | 93 | ) : null} 94 | 95 | > 96 | ); 97 | }; 98 | 99 | 100 | 101 | ```tsx 102 | import React, { useState } from "react"; 103 | import "ol/ol.css"; 104 | import { 105 | createBox, 106 | createRegularPolygon, 107 | GeometryFunction, 108 | } from "ol/interaction/Draw"; 109 | import OlSourceVector from "ol/source/Vector"; 110 | import Polygon from "ol/geom/Polygon"; 111 | import GeometryType from "ol/geom/GeometryType"; 112 | import { Geometry } from "ol/geom"; 113 | import { Map } from "@react-ol/fiber"; 114 | 115 | export function useGeometryFunction(shapeType) { 116 | if (["None", "Circle"].includes(shapeType)) { 117 | return; 118 | } 119 | if (shapeType === "Square") { 120 | return createRegularPolygon(4); 121 | } 122 | if (shapeType === "Box") { 123 | return createBox(); 124 | } 125 | if (shapeType === "Star") { 126 | return (coordinates, geometry) => { 127 | const center = coordinates[0]; 128 | const last = coordinates[1]; 129 | const dx = center[0] - last[0]; 130 | const dy = center[1] - last[1]; 131 | const radius = Math.sqrt(dx * dx + dy * dy); 132 | const rotation = Math.atan2(dy, dx); 133 | const newCoordinates = []; 134 | const numPoints = 12; 135 | for (let i = 0; i < numPoints; i += 1) { 136 | const angle = rotation + (i * 2 * Math.PI) / numPoints; 137 | const fraction = i % 2 === 0 ? 1 : 0.5; 138 | const offsetX = radius * fraction * Math.cos(angle); 139 | const offsetY = radius * fraction * Math.sin(angle); 140 | newCoordinates.push([center[0] + offsetX, center[1] + offsetY]); 141 | } 142 | newCoordinates.push(newCoordinates[0].slice()); 143 | if (!geometry) { 144 | geometry = new Polygon([newCoordinates]); 145 | } else { 146 | geometry.setCoordinates([newCoordinates]); 147 | } 148 | return geometry; 149 | }; 150 | } 151 | } 152 | 153 | export const ExampleDrawShapes = () => { 154 | const [shapeType, setShapeType] = useState("Circle"); 155 | const geometryFunction = useGeometryFunction(shapeType); 156 | const [vectorSource, setVectorSource] = useState(); 157 | return ( 158 | <> 159 | 160 | Geometry type: 161 | setShapeType(e.target.value)} 165 | > 166 | Circle 167 | Square 168 | Box 169 | Star 170 | None 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | {vectorSource && shapeType !== "None" ? ( 182 | 189 | ) : null} 190 | 191 | > 192 | ); 193 | }; 194 | ``` 195 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/dynamic-data.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Dynamic Data 2 | 3 | See https://openlayers.org/en/latest/examples/dynamic-data.html 4 | 5 | import React, { useRef } from "react"; 6 | import "ol/ol.css"; 7 | import { Map as OlMap } from "ol"; 8 | import { Style, Fill, Stroke, Circle as CircleStyle } from "ol/style"; 9 | import { Point, MultiPoint } from "ol/geom"; 10 | import { getVectorContext } from "ol/render"; 11 | import { Map } from "@react-ol/fiber"; 12 | 13 | export const imageStyle = new Style({ 14 | image: new CircleStyle({ 15 | radius: 5, 16 | fill: new Fill({ color: "yellow" }), 17 | stroke: new Stroke({ color: "red", width: 1 }), 18 | }), 19 | }); 20 | 21 | export const headInnerImageStyle = new Style({ 22 | image: new CircleStyle({ 23 | radius: 2, 24 | fill: new Fill({ color: "blue" }), 25 | }), 26 | }); 27 | 28 | export const headOuterImageStyle = new Style({ 29 | image: new CircleStyle({ 30 | radius: 5, 31 | fill: new Fill({ color: "black" }), 32 | }), 33 | }); 34 | 35 | export const n = 200; 36 | export const omegaTheta = 30000; 37 | export const R = 7e6; 38 | export const r = 2e6; 39 | export const p = 2e6; 40 | 41 | export const ExampleDynamicData = () => { 42 | const mapRef = useRef(null); 43 | const onPostrender = (event) => { 44 | const vectorContext = getVectorContext(event); 45 | const { frameState } = event; 46 | const theta = (2 * Math.PI * (frameState?.time ?? 0)) / omegaTheta; 47 | const coordinates = []; 48 | let i; 49 | for (i = 0; i < n; i += 1) { 50 | const t = theta + (2 * Math.PI * i) / n; 51 | const x = (R + r) * Math.cos(t) + p * Math.cos(((R + r) * t) / r); 52 | const y = (R + r) * Math.sin(t) + p * Math.sin(((R + r) * t) / r); 53 | coordinates.push([x, y]); 54 | } 55 | vectorContext.setStyle(imageStyle); 56 | vectorContext.drawGeometry(new MultiPoint(coordinates)); 57 | const headPoint = new Point(coordinates[coordinates.length - 1]); 58 | vectorContext.setStyle(headOuterImageStyle); 59 | vectorContext.drawGeometry(headPoint); 60 | vectorContext.setStyle(headInnerImageStyle); 61 | vectorContext.drawGeometry(headPoint); 62 | mapRef.current?.render(); 63 | return true; 64 | }; 65 | return ( 66 | 67 | 68 | 69 | 70 | 71 | 72 | ); 73 | }; 74 | 75 | 76 | 77 | ```tsx 78 | import React, { useRef } from "react"; 79 | import "ol/ol.css"; 80 | import { Map as OlMap } from "ol"; 81 | import { Style, Fill, Stroke, Circle as CircleStyle } from "ol/style"; 82 | import { Point, MultiPoint } from "ol/geom"; 83 | import { getVectorContext } from "ol/render"; 84 | import { Map } from "@react-ol/fiber"; 85 | 86 | export const imageStyle = new Style({ 87 | image: new CircleStyle({ 88 | radius: 5, 89 | fill: new Fill({ color: "yellow" }), 90 | stroke: new Stroke({ color: "red", width: 1 }), 91 | }), 92 | }); 93 | 94 | export const headInnerImageStyle = new Style({ 95 | image: new CircleStyle({ 96 | radius: 2, 97 | fill: new Fill({ color: "blue" }), 98 | }), 99 | }); 100 | 101 | export const headOuterImageStyle = new Style({ 102 | image: new CircleStyle({ 103 | radius: 5, 104 | fill: new Fill({ color: "black" }), 105 | }), 106 | }); 107 | 108 | export const n = 200; 109 | export const omegaTheta = 30000; 110 | export const R = 7e6; 111 | export const r = 2e6; 112 | export const p = 2e6; 113 | 114 | export const ExampleDynamicData = () => { 115 | const mapRef = useRef(null); 116 | const onPostrender = (event) => { 117 | const vectorContext = getVectorContext(event); 118 | const { frameState } = event; 119 | const theta = (2 * Math.PI * (frameState?.time ?? 0)) / omegaTheta; 120 | const coordinates = []; 121 | let i; 122 | for (i = 0; i < n; i += 1) { 123 | const t = theta + (2 * Math.PI * i) / n; 124 | const x = (R + r) * Math.cos(t) + p * Math.cos(((R + r) * t) / r); 125 | const y = (R + r) * Math.sin(t) + p * Math.sin(((R + r) * t) / r); 126 | coordinates.push([x, y]); 127 | } 128 | vectorContext.setStyle(imageStyle); 129 | vectorContext.drawGeometry(new MultiPoint(coordinates)); 130 | const headPoint = new Point(coordinates[coordinates.length - 1]); 131 | vectorContext.setStyle(headOuterImageStyle); 132 | vectorContext.drawGeometry(headPoint); 133 | vectorContext.setStyle(headInnerImageStyle); 134 | vectorContext.drawGeometry(headPoint); 135 | mapRef.current?.render(); 136 | return true; 137 | }; 138 | return ( 139 | 140 | 141 | 142 | 143 | 144 | 145 | ); 146 | }; 147 | ``` 148 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/geojson.en-US.mdx: -------------------------------------------------------------------------------- 1 | # GeoJSON 2 | 3 | See https://openlayers.org/en/latest/examples/geojson.html 4 | 5 | import React from "react"; 6 | import "ol/ol.css"; 7 | import GeoJSON from "ol/format/GeoJSON"; 8 | import { Circle as CircleStyle, Fill, Stroke, Style } from "ol/style"; 9 | import { Map } from "@react-ol/fiber"; 10 | 11 | export const image = new CircleStyle({ 12 | radius: 5, 13 | stroke: new Stroke({ color: "red", width: 1 }), 14 | }); 15 | 16 | export const styles = { 17 | Point: new Style({ 18 | image, 19 | }), 20 | LineString: new Style({ 21 | stroke: new Stroke({ 22 | color: "green", 23 | width: 1, 24 | }), 25 | }), 26 | MultiLineString: new Style({ 27 | stroke: new Stroke({ 28 | color: "green", 29 | width: 1, 30 | }), 31 | }), 32 | MultiPoint: new Style({ 33 | image, 34 | }), 35 | MultiPolygon: new Style({ 36 | stroke: new Stroke({ 37 | color: "yellow", 38 | width: 1, 39 | }), 40 | fill: new Fill({ 41 | color: "rgba(255, 255, 0, 0.1)", 42 | }), 43 | }), 44 | Polygon: new Style({ 45 | stroke: new Stroke({ 46 | color: "blue", 47 | lineDash: [4], 48 | width: 3, 49 | }), 50 | fill: new Fill({ 51 | color: "rgba(0, 0, 255, 0.1)", 52 | }), 53 | }), 54 | GeometryCollection: new Style({ 55 | stroke: new Stroke({ 56 | color: "magenta", 57 | width: 2, 58 | }), 59 | fill: new Fill({ 60 | color: "magenta", 61 | }), 62 | image: new CircleStyle({ 63 | radius: 10, 64 | stroke: new Stroke({ 65 | color: "magenta", 66 | }), 67 | }), 68 | }), 69 | Circle: new Style({ 70 | stroke: new Stroke({ 71 | color: "red", 72 | width: 2, 73 | }), 74 | fill: new Fill({ 75 | color: "rgba(255,0,0,0.2)", 76 | }), 77 | }), 78 | }; 79 | 80 | export const styleFunction = (feature) => { 81 | return styles[feature.getGeometry().getType()]; 82 | }; 83 | 84 | export const geojsonObject = { 85 | type: "FeatureCollection", 86 | crs: { 87 | type: "name", 88 | properties: { 89 | name: "EPSG:3857", 90 | }, 91 | }, 92 | features: [ 93 | { 94 | type: "Feature", 95 | geometry: { 96 | type: "Point", 97 | coordinates: [0, 0], 98 | }, 99 | }, 100 | { 101 | type: "Feature", 102 | geometry: { 103 | type: "LineString", 104 | coordinates: [ 105 | [4e6, -2e6], 106 | [8e6, 2e6], 107 | ], 108 | }, 109 | }, 110 | { 111 | type: "Feature", 112 | geometry: { 113 | type: "LineString", 114 | coordinates: [ 115 | [4e6, 2e6], 116 | [8e6, -2e6], 117 | ], 118 | }, 119 | }, 120 | { 121 | type: "Feature", 122 | geometry: { 123 | type: "Polygon", 124 | coordinates: [ 125 | [ 126 | [-5e6, -1e6], 127 | [-4e6, 1e6], 128 | [-3e6, -1e6], 129 | ], 130 | ], 131 | }, 132 | }, 133 | { 134 | type: "Feature", 135 | geometry: { 136 | type: "MultiLineString", 137 | coordinates: [ 138 | [ 139 | [-1e6, -7.5e5], 140 | [-1e6, 7.5e5], 141 | ], 142 | [ 143 | [1e6, -7.5e5], 144 | [1e6, 7.5e5], 145 | ], 146 | [ 147 | [-7.5e5, -1e6], 148 | [7.5e5, -1e6], 149 | ], 150 | [ 151 | [-7.5e5, 1e6], 152 | [7.5e5, 1e6], 153 | ], 154 | ], 155 | }, 156 | }, 157 | { 158 | type: "Feature", 159 | geometry: { 160 | type: "MultiPolygon", 161 | coordinates: [ 162 | [ 163 | [ 164 | [-5e6, 6e6], 165 | [-5e6, 8e6], 166 | [-3e6, 8e6], 167 | [-3e6, 6e6], 168 | ], 169 | ], 170 | [ 171 | [ 172 | [-2e6, 6e6], 173 | [-2e6, 8e6], 174 | [0, 8e6], 175 | [0, 6e6], 176 | ], 177 | ], 178 | [ 179 | [ 180 | [1e6, 6e6], 181 | [1e6, 8e6], 182 | [3e6, 8e6], 183 | [3e6, 6e6], 184 | ], 185 | ], 186 | ], 187 | }, 188 | }, 189 | { 190 | type: "Feature", 191 | geometry: { 192 | type: "GeometryCollection", 193 | geometries: [ 194 | { 195 | type: "LineString", 196 | coordinates: [ 197 | [-5e6, -5e6], 198 | [0, -5e6], 199 | ], 200 | }, 201 | { 202 | type: "Point", 203 | coordinates: [4e6, -5e6], 204 | }, 205 | { 206 | type: "Polygon", 207 | coordinates: [ 208 | [ 209 | [1e6, -6e6], 210 | [2e6, -4e6], 211 | [3e6, -6e6], 212 | ], 213 | ], 214 | }, 215 | ], 216 | }, 217 | }, 218 | ], 219 | }; 220 | 221 | export const GeoJSONExample = () => { 222 | return ( 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | ); 237 | }; 238 | 239 | 240 | 241 | ```tsx 242 | import React from "react"; 243 | import "ol/ol.css"; 244 | import GeoJSON from "ol/format/GeoJSON"; 245 | import { Circle as CircleStyle, Fill, Stroke, Style } from "ol/style"; 246 | import { Map } from "@react-ol/fiber"; 247 | 248 | export const image = new CircleStyle({ 249 | radius: 5, 250 | stroke: new Stroke({ color: "red", width: 1 }), 251 | }); 252 | 253 | export const styles = { 254 | Point: new Style({ 255 | image, 256 | }), 257 | LineString: new Style({ 258 | stroke: new Stroke({ 259 | color: "green", 260 | width: 1, 261 | }), 262 | }), 263 | MultiLineString: new Style({ 264 | stroke: new Stroke({ 265 | color: "green", 266 | width: 1, 267 | }), 268 | }), 269 | MultiPoint: new Style({ 270 | image, 271 | }), 272 | MultiPolygon: new Style({ 273 | stroke: new Stroke({ 274 | color: "yellow", 275 | width: 1, 276 | }), 277 | fill: new Fill({ 278 | color: "rgba(255, 255, 0, 0.1)", 279 | }), 280 | }), 281 | Polygon: new Style({ 282 | stroke: new Stroke({ 283 | color: "blue", 284 | lineDash: [4], 285 | width: 3, 286 | }), 287 | fill: new Fill({ 288 | color: "rgba(0, 0, 255, 0.1)", 289 | }), 290 | }), 291 | GeometryCollection: new Style({ 292 | stroke: new Stroke({ 293 | color: "magenta", 294 | width: 2, 295 | }), 296 | fill: new Fill({ 297 | color: "magenta", 298 | }), 299 | image: new CircleStyle({ 300 | radius: 10, 301 | stroke: new Stroke({ 302 | color: "magenta", 303 | }), 304 | }), 305 | }), 306 | Circle: new Style({ 307 | stroke: new Stroke({ 308 | color: "red", 309 | width: 2, 310 | }), 311 | fill: new Fill({ 312 | color: "rgba(255,0,0,0.2)", 313 | }), 314 | }), 315 | }; 316 | 317 | export const styleFunction = (feature) => { 318 | return styles[feature.getGeometry().getType()]; 319 | }; 320 | 321 | export const geojsonObject = { 322 | type: "FeatureCollection", 323 | crs: { 324 | type: "name", 325 | properties: { 326 | name: "EPSG:3857", 327 | }, 328 | }, 329 | features: [ 330 | { 331 | type: "Feature", 332 | geometry: { 333 | type: "Point", 334 | coordinates: [0, 0], 335 | }, 336 | }, 337 | { 338 | type: "Feature", 339 | geometry: { 340 | type: "LineString", 341 | coordinates: [ 342 | [4e6, -2e6], 343 | [8e6, 2e6], 344 | ], 345 | }, 346 | }, 347 | { 348 | type: "Feature", 349 | geometry: { 350 | type: "LineString", 351 | coordinates: [ 352 | [4e6, 2e6], 353 | [8e6, -2e6], 354 | ], 355 | }, 356 | }, 357 | { 358 | type: "Feature", 359 | geometry: { 360 | type: "Polygon", 361 | coordinates: [ 362 | [ 363 | [-5e6, -1e6], 364 | [-4e6, 1e6], 365 | [-3e6, -1e6], 366 | ], 367 | ], 368 | }, 369 | }, 370 | { 371 | type: "Feature", 372 | geometry: { 373 | type: "MultiLineString", 374 | coordinates: [ 375 | [ 376 | [-1e6, -7.5e5], 377 | [-1e6, 7.5e5], 378 | ], 379 | [ 380 | [1e6, -7.5e5], 381 | [1e6, 7.5e5], 382 | ], 383 | [ 384 | [-7.5e5, -1e6], 385 | [7.5e5, -1e6], 386 | ], 387 | [ 388 | [-7.5e5, 1e6], 389 | [7.5e5, 1e6], 390 | ], 391 | ], 392 | }, 393 | }, 394 | { 395 | type: "Feature", 396 | geometry: { 397 | type: "MultiPolygon", 398 | coordinates: [ 399 | [ 400 | [ 401 | [-5e6, 6e6], 402 | [-5e6, 8e6], 403 | [-3e6, 8e6], 404 | [-3e6, 6e6], 405 | ], 406 | ], 407 | [ 408 | [ 409 | [-2e6, 6e6], 410 | [-2e6, 8e6], 411 | [0, 8e6], 412 | [0, 6e6], 413 | ], 414 | ], 415 | [ 416 | [ 417 | [1e6, 6e6], 418 | [1e6, 8e6], 419 | [3e6, 8e6], 420 | [3e6, 6e6], 421 | ], 422 | ], 423 | ], 424 | }, 425 | }, 426 | { 427 | type: "Feature", 428 | geometry: { 429 | type: "GeometryCollection", 430 | geometries: [ 431 | { 432 | type: "LineString", 433 | coordinates: [ 434 | [-5e6, -5e6], 435 | [0, -5e6], 436 | ], 437 | }, 438 | { 439 | type: "Point", 440 | coordinates: [4e6, -5e6], 441 | }, 442 | { 443 | type: "Polygon", 444 | coordinates: [ 445 | [ 446 | [1e6, -6e6], 447 | [2e6, -4e6], 448 | [3e6, -6e6], 449 | ], 450 | ], 451 | }, 452 | ], 453 | }, 454 | }, 455 | ], 456 | }; 457 | 458 | export const GeoJSONExample = () => { 459 | return ( 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | ); 474 | }; 475 | ``` 476 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/icon.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Icon Symbolizer 2 | 3 | See https://openlayers.org/en/latest/examples/icon.html 4 | 5 | import React, { useState, useCallback } from "react"; 6 | import OverlayPositioning from "ol/OverlayPositioning"; 7 | import IconAnchorUnits from "ol/style/IconAnchorUnits"; 8 | import { Point } from "ol/geom"; 9 | import "ol/ol.css"; 10 | import { Map } from "@react-ol/fiber"; 11 | 12 | export const ExampleIcon = () => { 13 | const [coordinates, setCoordinates] = useState(undefined); 14 | const [map, setMap] = useState(null); 15 | const [popup, setPopup] = useState(null); 16 | const onClick = useCallback( 17 | (evt) => { 18 | if (!map) return; 19 | const feature = map.forEachFeatureAtPixel(evt.pixel, (f) => f); 20 | if (feature) { 21 | const coordinate = feature.getGeometry().getCoordinates(); 22 | setCoordinates(coordinate); 23 | } else { 24 | setCoordinates(undefined); 25 | } 26 | }, 27 | [map] 28 | ); 29 | const onPointermove = useCallback( 30 | (e) => { 31 | if (!map) return; 32 | const pixel = map.getEventPixel(e.originalEvent); 33 | const hit = map.hasFeatureAtPixel(pixel); 34 | map.getTarget().style.cursor = hit ? "pointer" : ""; 35 | }, 36 | [map] 37 | ); 38 | return ( 39 | <> 40 | 41 | 42 | Null Island 43 | 44 | 45 | 51 | {popup ? ( 52 | 61 | ) : null} 62 | 63 | 64 | 70 | 71 | 72 | 73 | 74 | 75 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | > 91 | ); 92 | }; 93 | 94 | 95 | 96 | ```tsx 97 | import React, { useState, useCallback } from "react"; 98 | import OverlayPositioning from "ol/OverlayPositioning"; 99 | import IconAnchorUnits from "ol/style/IconAnchorUnits"; 100 | import { Point } from "ol/geom"; 101 | import "ol/ol.css"; 102 | import { Map } from "@react-ol/fiber"; 103 | 104 | export const ExampleIcon = () => { 105 | const [coordinates, setCoordinates] = useState(undefined); 106 | const [map, setMap] = useState(null); 107 | const [popup, setPopup] = useState(null); 108 | const onClick = useCallback( 109 | (evt) => { 110 | if (!map) return; 111 | const feature = map.forEachFeatureAtPixel(evt.pixel, (f) => f); 112 | if (feature) { 113 | const coordinate = feature.getGeometry().getCoordinates(); 114 | setCoordinates(coordinate); 115 | } else { 116 | setCoordinates(undefined); 117 | } 118 | }, 119 | [map] 120 | ); 121 | const onPointermove = useCallback( 122 | (e) => { 123 | if (!map) return; 124 | const pixel = map.getEventPixel(e.originalEvent); 125 | const hit = map.hasFeatureAtPixel(pixel); 126 | map.getTarget().style.cursor = hit ? "pointer" : ""; 127 | }, 128 | [map] 129 | ); 130 | return ( 131 | <> 132 | 133 | 134 | Null Island 135 | 136 | 137 | 138 | {popup ? ( 139 | 148 | ) : null} 149 | 150 | 151 | 157 | 158 | 159 | 160 | 161 | 162 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | > 178 | ); 179 | }; 180 | ``` 181 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/image-filter.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Image Filters 2 | 3 | See https://openlayers.org/en/latest/examples/image-filter.html 4 | 5 | import React, { useState, useEffect, useCallback } from "react"; 6 | import { Map as OlMap } from "ol"; 7 | import { fromLonLat } from "ol/proj"; 8 | import "ol/ol.css"; 9 | import { Map } from "@react-ol/fiber"; 10 | 11 | export const kernels = { 12 | none: [0, 0, 0, 0, 1, 0, 0, 0, 0], 13 | sharpen: [0, -1, 0, -1, 5, -1, 0, -1, 0], 14 | sharpenless: [0, -1, 0, -1, 10, -1, 0, -1, 0], 15 | blur: [1, 1, 1, 1, 1, 1, 1, 1, 1], 16 | shadow: [1, 2, 1, 0, 1, 0, -1, -2, -1], 17 | emboss: [-2, 1, 0, -1, 1, 1, 0, 1, 2], 18 | edge: [0, 1, 0, 1, -4, 1, 0, 1, 0], 19 | }; 20 | 21 | export function normalize(kernel) { 22 | const len = kernel.length; 23 | const normal = new Array(len); 24 | let i; 25 | let sum = 0; 26 | for (i = 0; i < len; i += 1) { 27 | sum += kernel[i]; 28 | } 29 | if (sum <= 0) { 30 | normal.normalized = false; 31 | sum = 1; 32 | } else { 33 | normal.normalized = true; 34 | } 35 | for (i = 0; i < len; i += 1) { 36 | normal[i] = kernel[i] / sum; 37 | } 38 | return normal; 39 | } 40 | 41 | export function convolve(context, kernel) { 42 | const { width, height } = context.canvas; 43 | const size = Math.sqrt(kernel.length); 44 | const half = Math.floor(size / 2); 45 | const inputData = context.getImageData(0, 0, width, height).data; 46 | const output = context.createImageData(width, height); 47 | const outputData = output.data; 48 | for (let pixelY = 0; pixelY < height; pixelY += 1) { 49 | const pixelsAbove = pixelY * width; 50 | for (let pixelX = 0; pixelX < width; pixelX += 1) { 51 | let r = 0; 52 | let g = 0; 53 | let b = 0; 54 | let a = 0; 55 | for (let kernelY = 0; kernelY < size; kernelY += 1) { 56 | for (let kernelX = 0; kernelX < size; kernelX += 1) { 57 | const weight = kernel[kernelY * size + kernelX]; 58 | const neighborY = Math.min( 59 | height - 1, 60 | Math.max(0, pixelY + kernelY - half) 61 | ); 62 | const neighborX = Math.min( 63 | width - 1, 64 | Math.max(0, pixelX + kernelX - half) 65 | ); 66 | const inputIndex = (neighborY * width + neighborX) * 4; 67 | r += inputData[inputIndex] * weight; 68 | g += inputData[inputIndex + 1] * weight; 69 | b += inputData[inputIndex + 2] * weight; 70 | a += inputData[inputIndex + 3] * weight; 71 | } 72 | } 73 | const outputIndex = (pixelsAbove + pixelX) * 4; 74 | outputData[outputIndex] = r; 75 | outputData[outputIndex + 1] = g; 76 | outputData[outputIndex + 2] = b; 77 | outputData[outputIndex + 3] = kernel.normalized ? a : 255; 78 | } 79 | } 80 | context.putImageData(output, 0, 0); 81 | } 82 | 83 | export const ExampleImageFilter = () => { 84 | const [selectedKernel, setSelectedKernel] = useState("sharpen"); 85 | const [map, setMap] = useState(null); 86 | const onPostrender = useCallback( 87 | (event) => { 88 | const normalizedSelectedKernel = normalize(kernels[selectedKernel]); 89 | convolve(event.context, normalizedSelectedKernel); 90 | return true; 91 | }, 92 | [selectedKernel] 93 | ); 94 | useEffect(() => { 95 | if (!map) return; 96 | map.render(); 97 | }, [map, selectedKernel]); 98 | return ( 99 | <> 100 | setSelectedKernel(e.target.value)} 104 | > 105 | none 106 | sharpen 107 | sharpen less 108 | blur 109 | shadow 110 | emboss 111 | edge detect 112 | 113 | 114 | 115 | 116 | 120 | 121 | 122 | > 123 | ); 124 | }; 125 | 126 | 127 | 128 | ```tsx 129 | import React, { useState, useEffect, useCallback } from "react"; 130 | import { Map as OlMap } from "ol"; 131 | import { fromLonLat } from "ol/proj"; 132 | import "ol/ol.css"; 133 | import { Map } from "@react-ol/fiber"; 134 | 135 | export const kernels = { 136 | none: [0, 0, 0, 0, 1, 0, 0, 0, 0], 137 | sharpen: [0, -1, 0, -1, 5, -1, 0, -1, 0], 138 | sharpenless: [0, -1, 0, -1, 10, -1, 0, -1, 0], 139 | blur: [1, 1, 1, 1, 1, 1, 1, 1, 1], 140 | shadow: [1, 2, 1, 0, 1, 0, -1, -2, -1], 141 | emboss: [-2, 1, 0, -1, 1, 1, 0, 1, 2], 142 | edge: [0, 1, 0, 1, -4, 1, 0, 1, 0], 143 | }; 144 | 145 | export function normalize(kernel) { 146 | const len = kernel.length; 147 | const normal = new Array(len); 148 | let i; 149 | let sum = 0; 150 | for (i = 0; i < len; i += 1) { 151 | sum += kernel[i]; 152 | } 153 | if (sum <= 0) { 154 | normal.normalized = false; 155 | sum = 1; 156 | } else { 157 | normal.normalized = true; 158 | } 159 | for (i = 0; i < len; i += 1) { 160 | normal[i] = kernel[i] / sum; 161 | } 162 | return normal; 163 | } 164 | 165 | export function convolve(context, kernel) { 166 | const { width, height } = context.canvas; 167 | const size = Math.sqrt(kernel.length); 168 | const half = Math.floor(size / 2); 169 | const inputData = context.getImageData(0, 0, width, height).data; 170 | const output = context.createImageData(width, height); 171 | const outputData = output.data; 172 | for (let pixelY = 0; pixelY < height; pixelY += 1) { 173 | const pixelsAbove = pixelY * width; 174 | for (let pixelX = 0; pixelX < width; pixelX += 1) { 175 | let r = 0; 176 | let g = 0; 177 | let b = 0; 178 | let a = 0; 179 | for (let kernelY = 0; kernelY < size; kernelY += 1) { 180 | for (let kernelX = 0; kernelX < size; kernelX += 1) { 181 | const weight = kernel[kernelY * size + kernelX]; 182 | const neighborY = Math.min( 183 | height - 1, 184 | Math.max(0, pixelY + kernelY - half) 185 | ); 186 | const neighborX = Math.min( 187 | width - 1, 188 | Math.max(0, pixelX + kernelX - half) 189 | ); 190 | const inputIndex = (neighborY * width + neighborX) * 4; 191 | r += inputData[inputIndex] * weight; 192 | g += inputData[inputIndex + 1] * weight; 193 | b += inputData[inputIndex + 2] * weight; 194 | a += inputData[inputIndex + 3] * weight; 195 | } 196 | } 197 | const outputIndex = (pixelsAbove + pixelX) * 4; 198 | outputData[outputIndex] = r; 199 | outputData[outputIndex + 1] = g; 200 | outputData[outputIndex + 2] = b; 201 | outputData[outputIndex + 3] = kernel.normalized ? a : 255; 202 | } 203 | } 204 | context.putImageData(output, 0, 0); 205 | } 206 | 207 | export const ExampleImageFilter = () => { 208 | const [selectedKernel, setSelectedKernel] = useState("sharpen"); 209 | const [map, setMap] = useState(null); 210 | const onPostrender = useCallback( 211 | (event) => { 212 | const normalizedSelectedKernel = normalize(kernels[selectedKernel]); 213 | convolve(event.context, normalizedSelectedKernel); 214 | return true; 215 | }, 216 | [selectedKernel] 217 | ); 218 | useEffect(() => { 219 | if (!map) return; 220 | map.render(); 221 | }, [map, selectedKernel]); 222 | return ( 223 | <> 224 | setSelectedKernel(e.target.value)} 227 | > 228 | none 229 | sharpen 230 | sharpen less 231 | blur 232 | shadow 233 | emboss 234 | edge detect 235 | 236 | 237 | 238 | 239 | 243 | 244 | 245 | > 246 | ); 247 | }; 248 | ``` 249 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/mapbox-vector-tiles.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Mapbox Vector Tiles 2 | 3 | https://openlayers.org/en/latest/examples/mapbox-vector-tiles.html 4 | 5 | This uses the `createMapboxStreetsV6Style` function from https://openlayers.org/en/v6.13.0/examples/resources/mapbox-streets-v6-style.js 6 | 7 | export function createMapboxStreetsV6Style(e, t, o, a, r) { 8 | // Copied from https://openlayers.org/en/v6.13.0/examples/resources/mapbox-streets-v6-style.js 9 | var l = new t({ color: "" }), 10 | s = new o({ color: "", width: 1 }), 11 | n = new e({ fill: l }), 12 | i = new e({ fill: l, stroke: s }), 13 | d = new e({ stroke: s }), 14 | g = new e({ text: new r({ text: "", fill: l, stroke: s }) }), 15 | c = {}; 16 | function b(t) { 17 | var o = c[t]; 18 | return ( 19 | o || 20 | ((o = new e({ 21 | image: new a({ 22 | src: "https://unpkg.com/@mapbox/maki@4.0.0/icons/" + t + "-15.svg", 23 | imgSize: [15, 15], 24 | crossOrigin: "anonymous", 25 | }), 26 | })), 27 | (c[t] = o)), 28 | o 29 | ); 30 | } 31 | var m = []; 32 | return function (e, t) { 33 | var o = 0, 34 | a = e.get("layer"), 35 | r = e.get("class"), 36 | c = e.get("type"), 37 | C = e.get("scalerank"), 38 | x = e.get("labelrank"), 39 | h = e.get("admin_level"), 40 | p = e.get("maritime"), 41 | _ = e.get("disputed"), 42 | T = e.get("maki"), 43 | W = e.getGeometry().getType(); 44 | return ( 45 | "landuse" == a && "park" == r 46 | ? (l.setColor("#d8e8c8"), (m[o++] = n)) 47 | : "landuse" == a && "cemetery" == r 48 | ? (l.setColor("#e0e4dd"), (m[o++] = n)) 49 | : "landuse" == a && "hospital" == r 50 | ? (l.setColor("#fde"), (m[o++] = n)) 51 | : "landuse" == a && "school" == r 52 | ? (l.setColor("#f0e8f8"), (m[o++] = n)) 53 | : "landuse" == a && "wood" == r 54 | ? (l.setColor("rgb(233,238,223)"), (m[o++] = n)) 55 | : ("waterway" == a && "river" != r && "stream" != r && "canal" != r) || 56 | ("waterway" == a && "river" == r) 57 | ? (s.setColor("#a0c8f0"), s.setWidth(1), (m[o++] = d)) 58 | : "waterway" != a || ("stream" != r && "canal" != r) 59 | ? "water" == a 60 | ? (l.setColor("#a0c8f0"), (m[o++] = n)) 61 | : "aeroway" == a && "Polygon" == W 62 | ? (l.setColor("rgb(242,239,235)"), (m[o++] = n)) 63 | : "aeroway" == a && "LineString" == W && t <= 76.43702828517625 64 | ? (s.setColor("#f0ede9"), s.setWidth(1), (m[o++] = d)) 65 | : "building" == a 66 | ? (l.setColor("#f2eae2"), 67 | s.setColor("#dfdbd7"), 68 | s.setWidth(1), 69 | (m[o++] = i)) 70 | : "tunnel" == a && "motorway_link" == r 71 | ? (s.setColor("#e9ac77"), s.setWidth(1), (m[o++] = d)) 72 | : "tunnel" == a && "service" == r 73 | ? (s.setColor("#cfcdca"), s.setWidth(1), (m[o++] = d)) 74 | : "tunnel" != a || ("street" != r && "street_limited" != r) 75 | ? ("tunnel" == a && "main" == r && t <= 1222.99245256282) || 76 | ("tunnel" == a && "motorway" == r) 77 | ? (s.setColor("#e9ac77"), s.setWidth(1), (m[o++] = d)) 78 | : "tunnel" == a && "path" == r 79 | ? (s.setColor("#cba"), s.setWidth(1), (m[o++] = d)) 80 | : "tunnel" == a && "major_rail" == r 81 | ? (s.setColor("#bbb"), s.setWidth(2), (m[o++] = d)) 82 | : "road" == a && "motorway_link" == r 83 | ? (s.setColor("#e9ac77"), s.setWidth(1), (m[o++] = d)) 84 | : "road" != a || 85 | ("street" != r && "street_limited" != r) || 86 | "LineString" != W 87 | ? ("road" == a && "main" == r && t <= 1222.99245256282) || 88 | ("road" == a && "motorway" == r && t <= 4891.96981025128) 89 | ? (s.setColor("#e9ac77"), s.setWidth(1), (m[o++] = d)) 90 | : "road" == a && "path" == r 91 | ? (s.setColor("#cba"), s.setWidth(1), (m[o++] = d)) 92 | : "road" == a && "major_rail" == r 93 | ? (s.setColor("#bbb"), s.setWidth(2), (m[o++] = d)) 94 | : ("bridge" == a && "motorway_link" == r) || 95 | ("bridge" == a && "motorway" == r) 96 | ? (s.setColor("#e9ac77"), s.setWidth(1), (m[o++] = d)) 97 | : "bridge" == a && "service" == r 98 | ? (s.setColor("#cfcdca"), s.setWidth(1), (m[o++] = d)) 99 | : "bridge" != a || ("street" != r && "street_limited" != r) 100 | ? "bridge" == a && "main" == r && t <= 1222.99245256282 101 | ? (s.setColor("#e9ac77"), s.setWidth(1), (m[o++] = d)) 102 | : "bridge" == a && "path" == r 103 | ? (s.setColor("#cba"), s.setWidth(1), (m[o++] = d)) 104 | : "bridge" == a && "major_rail" == r 105 | ? (s.setColor("#bbb"), s.setWidth(2), (m[o++] = d)) 106 | : ("admin" == a && h >= 3 && 0 === p) || 107 | ("admin" == a && 2 == h && 0 === _ && 0 === p) || 108 | ("admin" == a && 2 == h && 1 === _ && 0 === p) 109 | ? (s.setColor("#9e9cab"), s.setWidth(1), (m[o++] = d)) 110 | : ("admin" == a && h >= 3 && 1 === p) || 111 | ("admin" == a && 2 == h && 1 === p) 112 | ? (s.setColor("#a0c8f0"), s.setWidth(1), (m[o++] = d)) 113 | : "country_label" == a && 1 === C 114 | ? (g.getText().setText(e.get("name_en")), 115 | g 116 | .getText() 117 | .setFont('bold 11px "Open Sans", "Arial Unicode MS"'), 118 | l.setColor("#334"), 119 | s.setColor("rgba(255,255,255,0.8)"), 120 | s.setWidth(2), 121 | (m[o++] = g)) 122 | : "country_label" == a && 2 === C && t <= 19567.87924100512 123 | ? (g.getText().setText(e.get("name_en")), 124 | g 125 | .getText() 126 | .setFont('bold 10px "Open Sans", "Arial Unicode MS"'), 127 | l.setColor("#334"), 128 | s.setColor("rgba(255,255,255,0.8)"), 129 | s.setWidth(2), 130 | (m[o++] = g)) 131 | : "country_label" == a && 3 === C && t <= 9783.93962050256 132 | ? (g.getText().setText(e.get("name_en")), 133 | g 134 | .getText() 135 | .setFont('bold 9px "Open Sans", "Arial Unicode MS"'), 136 | l.setColor("#334"), 137 | s.setColor("rgba(255,255,255,0.8)"), 138 | s.setWidth(2), 139 | (m[o++] = g)) 140 | : "country_label" == a && 4 === C && t <= 4891.96981025128 141 | ? (g.getText().setText(e.get("name_en")), 142 | g 143 | .getText() 144 | .setFont('bold 8px "Open Sans", "Arial Unicode MS"'), 145 | l.setColor("#334"), 146 | s.setColor("rgba(255,255,255,0.8)"), 147 | s.setWidth(2), 148 | (m[o++] = g)) 149 | : ("marine_label" == a && 1 === x && "Point" == W) || 150 | ("marine_label" == a && 2 === x && "Point" == W) 151 | ? (g.getText().setText(e.get("name_en")), 152 | g 153 | .getText() 154 | .setFont('italic 11px "Open Sans", "Arial Unicode MS"'), 155 | l.setColor("#74aee9"), 156 | s.setColor("rgba(255,255,255,0.8)"), 157 | s.setWidth(1), 158 | (m[o++] = g)) 159 | : "marine_label" == a && 3 === x && "Point" == W 160 | ? (g.getText().setText(e.get("name_en")), 161 | g 162 | .getText() 163 | .setFont('italic 10px "Open Sans", "Arial Unicode MS"'), 164 | l.setColor("#74aee9"), 165 | s.setColor("rgba(255,255,255,0.8)"), 166 | s.setWidth(1), 167 | (m[o++] = g)) 168 | : "marine_label" == a && 4 === x && "Point" == W 169 | ? (g.getText().setText(e.get("name_en")), 170 | g 171 | .getText() 172 | .setFont('italic 9px "Open Sans", "Arial Unicode MS"'), 173 | l.setColor("#74aee9"), 174 | s.setColor("rgba(255,255,255,0.8)"), 175 | s.setWidth(1), 176 | (m[o++] = g)) 177 | : "place_label" == a && "city" == c && t <= 1222.99245256282 178 | ? (g.getText().setText(e.get("name_en")), 179 | g.getText().setFont('11px "Open Sans", "Arial Unicode MS"'), 180 | l.setColor("#333"), 181 | s.setColor("rgba(255,255,255,0.8)"), 182 | s.setWidth(1), 183 | (m[o++] = g)) 184 | : "place_label" == a && "town" == c && t <= 305.748113140705 185 | ? (g.getText().setText(e.get("name_en")), 186 | g.getText().setFont('9px "Open Sans", "Arial Unicode MS"'), 187 | l.setColor("#333"), 188 | s.setColor("rgba(255,255,255,0.8)"), 189 | s.setWidth(1), 190 | (m[o++] = g)) 191 | : "place_label" == a && "village" == c && t <= 38.21851414258813 192 | ? (g.getText().setText(e.get("name_en")), 193 | g.getText().setFont('8px "Open Sans", "Arial Unicode MS"'), 194 | l.setColor("#333"), 195 | s.setColor("rgba(255,255,255,0.8)"), 196 | s.setWidth(1), 197 | (m[o++] = g)) 198 | : "place_label" == a && 199 | t <= 19.109257071294063 && 200 | ("hamlet" == c || "suburb" == c || "neighbourhood" == c) 201 | ? (g.getText().setText(e.get("name_en")), 202 | g.getText().setFont('bold 9px "Arial Narrow"'), 203 | l.setColor("#633"), 204 | s.setColor("rgba(255,255,255,0.8)"), 205 | s.setWidth(1), 206 | (m[o++] = g)) 207 | : (("poi_label" == a && 208 | t <= 19.109257071294063 && 209 | 1 == C && 210 | "marker" !== T) || 211 | ("poi_label" == a && 212 | t <= 9.554628535647032 && 213 | 2 == C && 214 | "marker" !== T) || 215 | ("poi_label" == a && 216 | t <= 4.777314267823516 && 217 | 3 == C && 218 | "marker" !== T) || 219 | ("poi_label" == a && 220 | t <= 2.388657133911758 && 221 | 4 == C && 222 | "marker" !== T) || 223 | ("poi_label" == a && 224 | t <= 1.194328566955879 && 225 | C >= 5 && 226 | "marker" !== T)) && 227 | (m[o++] = b(T)) 228 | : (s.setColor("#cfcdca"), s.setWidth(1), (m[o++] = d)) 229 | : (s.setColor("#cfcdca"), s.setWidth(1), (m[o++] = d)) 230 | : (s.setColor("#cfcdca"), s.setWidth(1), (m[o++] = d)) 231 | : (s.setColor("#a0c8f0"), s.setWidth(1), (m[o++] = d)), 232 | (m.length = o), 233 | m 234 | ); 235 | }; 236 | } 237 | 238 | import React from "react"; 239 | import "ol/ol.css"; 240 | import { Map } from "@react-ol/fiber"; 241 | import MVT from "ol/format/MVT"; 242 | import { Fill, Icon, Stroke, Style, Text } from "ol/style"; 243 | import olms from "ol-mapbox-style"; 244 | import { defaultResolutions } from "ol-mapbox-style/dist/util"; 245 | 246 | export const key = process.env.NEXT_PUBLIC_MAPBOX_KEY; 247 | export const format = new MVT(); 248 | export const attributions = 249 | '© Mapbox © OpenStreetMap contributors'; 250 | export const style = createMapboxStreetsV6Style( 251 | Style, 252 | Fill, 253 | Stroke, 254 | Icon, 255 | Text 256 | ); 257 | 258 | export const ExampleMapboxVectorTiles = () => ( 259 | 260 | 261 | 262 | 267 | 268 | 269 | ); 270 | 271 | 272 | 273 | ```tsx 274 | import React from "react"; 275 | import "ol/ol.css"; 276 | import { Map } from "@react-ol/fiber"; 277 | import MVT from "ol/format/MVT"; 278 | import { Fill, Icon, Stroke, Style, Text } from "ol/style"; 279 | import olms from "ol-mapbox-style"; 280 | import { defaultResolutions } from "ol-mapbox-style/dist/util"; 281 | 282 | export function createMapboxStreetsV6Style(e, t, o, a, r) { 283 | // ... 284 | } 285 | 286 | export const key = process.env.NEXT_PUBLIC_MAPBOX_KEY; 287 | export const format = new MVT(); 288 | export const attributions = 289 | '© Mapbox © OpenStreetMap contributors'; 290 | export const style = createMapboxStreetsV6Style( 291 | Style, 292 | Fill, 293 | Stroke, 294 | Icon, 295 | Text 296 | ); 297 | 298 | export const ExampleMapboxVectorTiles = () => ( 299 | 300 | 301 | 302 | 307 | 308 | 309 | ); 310 | ``` 311 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/meta.en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "accessible": "Accessible Map", 3 | "bing-maps": "Bing Maps", 4 | "cluster": "Clustered Features", 5 | "draw-and-modify-features": "Draw and Modify Features", 6 | "draw-shapes": "Draw Shapes", 7 | "dynamic-data": "Dynamic Data", 8 | "geojson": "GeoJSON", 9 | "icon": "Icon Symbolizer", 10 | "image-filter": "Image Filters", 11 | "mapbox-vector-tiles": "Mapbox Vector Tiles", 12 | "popup": "Popup", 13 | "preload": "Preload Tiles", 14 | "reprojection-image": "Image Reprojection", 15 | "reprojection-wgs84": "OpenStreetMap Reprojection", 16 | "select-features": "Select Features", 17 | "simple": "Simple Map", 18 | "static-image": "Static Image", 19 | "turf": "turf.js", 20 | "wms-tiled": "Tiled WMS", 21 | "xyz-retina": "XYZ Retina Tiles", 22 | "xyz": "XYZ", 23 | "zoomify": "Zoomify" 24 | } 25 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/popup.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Popup 2 | 3 | See https://openlayers.org/en/latest/examples/popup.html 4 | 5 | import React, { useState, useCallback } from "react"; 6 | import "ol/ol.css"; 7 | import { toLonLat } from "ol/proj"; 8 | import { toStringHDMS } from "ol/coordinate"; 9 | import { Map } from "@react-ol/fiber"; 10 | 11 | export const ExamplePopup = () => { 12 | const [coordinates, setCoordinates] = useState(undefined); 13 | const [popup, setPopup] = useState(); 14 | const onSingleclick = useCallback((evt) => { 15 | const { coordinate } = evt; 16 | setCoordinates(coordinate); 17 | }, []); 18 | return ( 19 | <> 20 | 32 | { 35 | setCoordinates(undefined); 36 | e.target.blur(); 37 | return false; 38 | }} 39 | style={{ 40 | textDecoration: "none", 41 | position: "absolute", 42 | top: "2px", 43 | right: "8px", 44 | }} 45 | > 46 | ✖ 47 | 48 | 49 | You clicked here: 50 | {coordinates && toStringHDMS(toLonLat(coordinates))} 51 | 52 | 53 | 57 | {popup ? ( 58 | 66 | ) : null} 67 | 68 | 69 | © OpenStreetMap contributors' 72 | } 73 | url="https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png" 74 | tileSize={512} 75 | /> 76 | 77 | 78 | > 79 | ); 80 | }; 81 | 82 | 83 | 84 | ```tsx 85 | import React, { useState, useCallback } from "react"; 86 | import "ol/ol.css"; 87 | import { toLonLat } from "ol/proj"; 88 | import { toStringHDMS } from "ol/coordinate"; 89 | import { Map } from "@react-ol/fiber"; 90 | 91 | export const ExamplePopup = () => { 92 | const [coordinates, setCoordinates] = useState(undefined); 93 | const [popup, setPopup] = useState(); 94 | const onSingleclick = useCallback((evt) => { 95 | const { coordinate } = evt; 96 | setCoordinates(coordinate); 97 | }, []); 98 | return ( 99 | <> 100 | 112 | { 115 | setCoordinates(undefined); 116 | e.target.blur(); 117 | return false; 118 | }} 119 | style={{ 120 | textDecoration: "none", 121 | position: "absolute", 122 | top: "2px", 123 | right: "8px", 124 | }} 125 | > 126 | ✖ 127 | 128 | 129 | You clicked here: 130 | {coordinates && toStringHDMS(toLonLat(coordinates))} 131 | 132 | 133 | 134 | {popup ? ( 135 | 143 | ) : null} 144 | 145 | 146 | © OpenStreetMap contributors' 149 | } 150 | url="https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png" 151 | tileSize={512} 152 | crossOrigin={null} 153 | /> 154 | 155 | 156 | > 157 | ); 158 | }; 159 | ``` 160 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/preload.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Preload Tiles 2 | 3 | See https://openlayers.org/en/latest/examples/preload.html 4 | 5 | import React from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | 9 | export const ExamplePreload = () => ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | 19 | 20 | ```tsx 21 | import React from "react"; 22 | import "ol/ol.css"; 23 | import { Map } from "@react-ol/fiber"; 24 | 25 | export const ExamplePreload = () => ( 26 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | ``` 34 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/reprojection-image.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Image Reprojection 2 | 3 | See https://openlayers.org/en/latest/examples/reprojection-image.html 4 | 5 | import React, { useState } from "react"; 6 | import proj4 from "proj4"; 7 | import { getCenter } from "ol/extent"; 8 | import { register } from "ol/proj/proj4"; 9 | import { transform } from "ol/proj"; 10 | import "ol/ol.css"; 11 | import { Map } from "@react-ol/fiber"; 12 | 13 | export const dummy1 = proj4.defs( 14 | "EPSG:27700", 15 | "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 " + 16 | "+x_0=400000 +y_0=-100000 +ellps=airy " + 17 | "+towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 " + 18 | "+units=m +no_defs" 19 | ); 20 | export const dummy2 = register(proj4); 21 | 22 | export const ExampleReprojectionImage = () => { 23 | const [isImageSmooth, setImageSmoothing] = useState(true); 24 | const imageExtent = [0, 0, 700000, 1300000]; 25 | return ( 26 | <> 27 | 28 | setImageSmoothing(e.target.checked)} 33 | /> 34 | Image smoothing 35 | 36 | 37 | 45 | 46 | 47 | 48 | 49 | 58 | 59 | 60 | > 61 | ); 62 | }; 63 | 64 | 65 | 66 | ```tsx 67 | import React, { useState } from "react"; 68 | import proj4 from "proj4"; 69 | import { getCenter } from "ol/extent"; 70 | import { register } from "ol/proj/proj4"; 71 | import { transform } from "ol/proj"; 72 | import "ol/ol.css"; 73 | import { Map } from "@react-ol/fiber"; 74 | 75 | proj4.defs( 76 | "EPSG:27700", 77 | "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 " + 78 | "+x_0=400000 +y_0=-100000 +ellps=airy " + 79 | "+towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 " + 80 | "+units=m +no_defs" 81 | ); 82 | register(proj4); 83 | 84 | export const ExampleReprojectionImage = () => { 85 | const [isImageSmooth, setImageSmoothing] = useState(true); 86 | const imageExtent = [0, 0, 700000, 1300000]; 87 | return ( 88 | <> 89 | 90 | setImageSmoothing(e.target.checked)} 95 | /> 96 | Image smoothing 97 | 98 | 99 | 107 | 108 | 109 | 110 | 111 | 120 | 121 | 122 | > 123 | ); 124 | }; 125 | ``` 126 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/reprojection-wgs84.en-US.mdx: -------------------------------------------------------------------------------- 1 | # OpenStreetMap Reprojection 2 | 3 | See hhttps://openlayers.org/en/latest/examples/reprojection-wgs84.html 4 | 5 | import React from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | 9 | export const ExampleReprojectionWGS84 = () => ( 10 | 11 | 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | 23 | 24 | ```tsx 25 | import React from "react"; 26 | import "ol/ol.css"; 27 | import { Map } from "@react-ol/fiber"; 28 | 29 | export const ExampleReprojectionWGS84 = () => ( 30 | 31 | 36 | 37 | 38 | 39 | 40 | ); 41 | ``` 42 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/select-features.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Select Features 2 | 3 | See https://openlayers.org/en/latest/examples/select-features.html 4 | 5 | import React, { useEffect, useState, useMemo, useCallback } from "react"; 6 | import "ol/ol.css"; 7 | import GeoJSON from "ol/format/GeoJSON"; 8 | import { click, pointerMove, altKeyOnly } from "ol/events/condition"; 9 | import { Fill, Stroke, Style } from "ol/style"; 10 | import { Map } from "@react-ol/fiber"; 11 | 12 | export const format = new GeoJSON(); 13 | 14 | export const style = new Style({ 15 | fill: new Fill({ 16 | color: "#eeeeee", 17 | }), 18 | }); 19 | 20 | export const selectedStyle = new Style({ 21 | fill: new Fill({ 22 | color: "#eeeeee", 23 | }), 24 | stroke: new Stroke({ 25 | color: "rgba(255, 255, 255, 0.7)", 26 | width: 2, 27 | }), 28 | }); 29 | 30 | export const styleFunction = (feature) => { 31 | const color = feature.get("COLOR") || "#eeeeee"; 32 | style.getFill().setColor(color); 33 | return style; 34 | }; 35 | 36 | export const selectedStyleFunction = (feature) => { 37 | const color = feature.get("COLOR") || "#eeeeee"; 38 | selectedStyle.getFill().setColor(color); 39 | return selectedStyle; 40 | }; 41 | 42 | export const ExampleSelectFeatures = () => { 43 | const [displayText, setDisplayText] = useState("0 selected features"); 44 | const [selectMethod, setSelectMethod] = useState("singleClick"); 45 | const handleSelect = useCallback((e) => { 46 | setDisplayText( 47 | ` ${e.target 48 | .getFeatures() 49 | .getLength()} selected features (last operation selected ${ 50 | e.selected.length 51 | } and deselected ${e.deselected.length} features)` 52 | ); 53 | }, []); 54 | const selectCondition = useMemo(() => { 55 | switch (selectMethod) { 56 | case "singleClick": 57 | return click; 58 | case "pointerMove": 59 | return pointerMove; 60 | case "altClick": 61 | return (mapBrowserEvent) => { 62 | return click(mapBrowserEvent) && altKeyOnly(mapBrowserEvent); 63 | }; 64 | default: 65 | return () => false; 66 | } 67 | }, [selectMethod]); 68 | return ( 69 | <> 70 | 71 | Action type 72 | setSelectMethod(e.target.value)} 75 | className="mt-4 block w-full pl-3 pr-10 py-2 text-base dark:text-gray-900 border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md" 76 | > 77 | 78 | Click 79 | 80 | Single-click 81 | Hover 82 | Alt+Click 83 | None 84 | 85 | {displayText} 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 97 | 98 | 103 | 104 | > 105 | ); 106 | }; 107 | 108 | 109 | 110 | ```tsx 111 | import React, { useEffect, useState, useMemo, useCallback } from "react"; 112 | import "ol/ol.css"; 113 | import GeoJSON from "ol/format/GeoJSON"; 114 | import { click, pointerMove, altKeyOnly } from "ol/events/condition"; 115 | import { Fill, Stroke, Style } from "ol/style"; 116 | import { Map } from "@react-ol/fiber"; 117 | 118 | export const format = new GeoJSON(); 119 | 120 | export const style = new Style({ 121 | fill: new Fill({ 122 | color: "#eeeeee", 123 | }), 124 | }); 125 | 126 | export const selectedStyle = new Style({ 127 | fill: new Fill({ 128 | color: "#eeeeee", 129 | }), 130 | stroke: new Stroke({ 131 | color: "rgba(255, 255, 255, 0.7)", 132 | width: 2, 133 | }), 134 | }); 135 | 136 | export const styleFunction = (feature) => { 137 | const color = feature.get("COLOR") || "#eeeeee"; 138 | style.getFill().setColor(color); 139 | return style; 140 | }; 141 | 142 | export const selectedStyleFunction = (feature) => { 143 | const color = feature.get("COLOR") || "#eeeeee"; 144 | selectedStyle.getFill().setColor(color); 145 | return selectedStyle; 146 | }; 147 | 148 | export const ExampleSelectFeatures = () => { 149 | const [displayText, setDisplayText] = useState("0 selected features"); 150 | const [selectMethod, setSelectMethod] = useState("singleClick"); 151 | const handleSelect = useCallback((e) => { 152 | setDisplayText( 153 | ` ${e.target 154 | .getFeatures() 155 | .getLength()} selected features (last operation selected ${ 156 | e.selected.length 157 | } and deselected ${e.deselected.length} features)` 158 | ); 159 | }, []); 160 | const selectCondition = useMemo(() => { 161 | switch (selectMethod) { 162 | case "singleClick": 163 | return click; 164 | case "pointerMove": 165 | return pointerMove; 166 | case "altClick": 167 | return (mapBrowserEvent) => { 168 | return click(mapBrowserEvent) && altKeyOnly(mapBrowserEvent); 169 | }; 170 | default: 171 | return () => false; 172 | } 173 | }, [selectMethod]); 174 | return ( 175 | <> 176 | 177 | Action type 178 | setSelectMethod(e.target.value)}> 179 | 180 | Click 181 | 182 | Single-click 183 | Hover 184 | Alt+Click 185 | None 186 | 187 | {displayText} 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 199 | 200 | 205 | 206 | > 207 | ); 208 | }; 209 | ``` 210 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/simple.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Simple Map 2 | 3 | See https://openlayers.org/en/latest/examples/simple.html 4 | 5 | import React from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | 9 | export const ExampleSimple = () => ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | 19 | 20 | ```tsx 21 | import React from "react"; 22 | import "ol/ol.css"; 23 | import { Map } from "@react-ol/fiber"; 24 | 25 | export const ExampleSimple = () => ( 26 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | ``` 34 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/static-image.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Static Image 2 | 3 | See https://openlayers.org/en/latest/examples/static-image.html 4 | 5 | import React from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | import { getCenter } from "ol/extent"; 9 | import Projection from "ol/proj/Projection"; 10 | 11 | export const extent = [0, 0, 1024, 968]; 12 | export const attributions = '© xkcd'; 13 | export const projection = new Projection({ 14 | code: "xkcd-image", 15 | units: "pixels", 16 | extent, 17 | }); 18 | 19 | export const ExampleStaticImage = () => ( 20 | 21 | 27 | 28 | 34 | 35 | 36 | ); 37 | 38 | 39 | 40 | ```tsx 41 | import React from "react"; 42 | import "ol/ol.css"; 43 | import { Map } from "@react-ol/fiber"; 44 | import { getCenter } from "ol/extent"; 45 | import Projection from "ol/proj/Projection"; 46 | 47 | export const extent = [0, 0, 1024, 968]; 48 | export const attributions = '© xkcd'; 49 | export const projection = new Projection({ 50 | code: "xkcd-image", 51 | units: "pixels", 52 | extent, 53 | }); 54 | 55 | export const ExampleStaticImage = () => ( 56 | 57 | 63 | 64 | 70 | 71 | 72 | ); 73 | ``` 74 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/turf.en-US.mdx: -------------------------------------------------------------------------------- 1 | # turf.js 2 | 3 | See https://openlayers.org/en/latest/examples/turf.html 4 | 5 | import React, { useEffect, useState } from "react"; 6 | import "ol/ol.css"; 7 | import { Vector } from "ol/source"; 8 | import { fromLonLat } from "ol/proj"; 9 | import GeoJSON from "ol/format/GeoJSON"; 10 | import { Geometry } from "ol/geom"; 11 | import { lineDistance, along } from "@turf/turf"; 12 | import { Map } from "@react-ol/fiber"; 13 | 14 | export const ExampleTurf = () => { 15 | const [vectorSource, setVectorSource] = useState(); 16 | useEffect(() => { 17 | if (!vectorSource) return; 18 | fetch( 19 | "https://openlayers.org/en/latest/examples/data/geojson/roads-seoul.geojson" 20 | ) 21 | .then((res) => res.json()) 22 | .then((json) => { 23 | const format = new GeoJSON(); 24 | const features = format.readFeatures(json); 25 | const street = features[0]; 26 | // convert to a turf.js feature 27 | const turfLine = format.writeFeatureObject(street); 28 | // show a marker every 200 meters 29 | const distance = 0.2; 30 | // get the line length in kilometers 31 | const length = lineDistance(turfLine, { units: "kilometers" }); 32 | for (let i = 1; i <= length / distance; i += 1) { 33 | const turfPoint = along(turfLine, i * distance, { 34 | units: "kilometers", 35 | }); 36 | // convert the generated point to a OpenLayers feature 37 | const marker = format.readFeature(turfPoint); 38 | marker.getGeometry().transform("EPSG:4326", "EPSG:3857"); 39 | vectorSource.addFeature(marker); 40 | } 41 | street.getGeometry().transform("EPSG:4326", "EPSG:3857"); 42 | vectorSource.addFeature(street); 43 | }) 44 | .catch((e) => { 45 | throw e; 46 | }); 47 | }, [vectorSource]); 48 | return ( 49 | 50 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | ); 62 | }; 63 | 64 | 65 | 66 | ```tsx 67 | import React, { useEffect, useState } from "react"; 68 | import "ol/ol.css"; 69 | import { Vector } from "ol/source"; 70 | import { fromLonLat } from "ol/proj"; 71 | import GeoJSON from "ol/format/GeoJSON"; 72 | import { Geometry } from "ol/geom"; 73 | import { lineDistance, along } from "@turf/turf"; 74 | import { Map } from "@react-ol/fiber"; 75 | 76 | export const ExampleTurf = () => { 77 | const [vectorSource, setVectorSource] = useState(); 78 | useEffect(() => { 79 | if (!vectorSource) return; 80 | fetch( 81 | "https://openlayers.org/en/latest/examples/data/geojson/roads-seoul.geojson" 82 | ) 83 | .then((res) => res.json()) 84 | .then((json) => { 85 | const format = new GeoJSON(); 86 | const features = format.readFeatures(json); 87 | const street = features[0]; 88 | // convert to a turf.js feature 89 | const turfLine = format.writeFeatureObject(street); 90 | // show a marker every 200 meters 91 | const distance = 0.2; 92 | // get the line length in kilometers 93 | const length = lineDistance(turfLine, { units: "kilometers" }); 94 | for (let i = 1; i <= length / distance; i += 1) { 95 | const turfPoint = along(turfLine, i * distance, { 96 | units: "kilometers", 97 | }); 98 | // convert the generated point to a OpenLayers feature 99 | const marker = format.readFeature(turfPoint); 100 | marker.getGeometry().transform("EPSG:4326", "EPSG:3857"); 101 | vectorSource.addFeature(marker); 102 | } 103 | street.getGeometry().transform("EPSG:4326", "EPSG:3857"); 104 | vectorSource.addFeature(street); 105 | }) 106 | .catch((e) => { 107 | throw e; 108 | }); 109 | }, [vectorSource]); 110 | return ( 111 | 112 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | ); 124 | }; 125 | ``` 126 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/wms-tiled.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Tiled WMS 2 | 3 | See https://openlayers.org/en/latest/examples/wms-tiled.html 4 | 5 | import React from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | 9 | export const ExampleTiledWMS = () => { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | 29 | 30 | ```tsx 31 | import React from "react"; 32 | import "ol/ol.css"; 33 | import { Map } from "@react-ol/fiber"; 34 | 35 | export const ExampleTiledWMS = () => { 36 | return ( 37 | 38 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | ); 52 | }; 53 | ``` 54 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/xyz-retina.en-US.mdx: -------------------------------------------------------------------------------- 1 | # XYZ Retina Tiles 2 | 3 | See https://openlayers.org/en/latest/examples/xyz-retina.html 4 | 5 | import React from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | import { transform } from "ol/proj"; 9 | 10 | export const key = process.env.NEXT_PUBLIC_MAPTILER_KEY; 11 | export const attributions = 12 | '© MapTiler ' + 13 | '© OpenStreetMap contributors'; 14 | 15 | export const ExampleXYZRetina = () => { 16 | return ( 17 | 18 | 27 | 28 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | 42 | 43 | ```tsx 44 | import React from "react"; 45 | import "ol/ol.css"; 46 | import { Map } from "@react-ol/fiber"; 47 | import { transform } from "ol/proj"; 48 | 49 | export const key = process.env.NEXT_PUBLIC_MAPTILER_KEY; 50 | export const attributions = 51 | '© MapTiler ' + 52 | '© OpenStreetMap contributors'; 53 | 54 | export const ExampleXYZRetina = () => { 55 | return ( 56 | 57 | 66 | 67 | 75 | 76 | 77 | ); 78 | }; 79 | ``` 80 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/xyz.en-US.mdx: -------------------------------------------------------------------------------- 1 | # XYZ 2 | 3 | See https://openlayers.org/en/latest/examples/xyz.html 4 | 5 | import React from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | 9 | export const key = process.env.NEXT_PUBLIC_THUNDERFOREST_KEY; 10 | 11 | export const ExampleXYZ = () => { 12 | return ( 13 | 14 | 15 | 16 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | 29 | 30 | ```tsx 31 | import React from "react"; 32 | import "ol/ol.css"; 33 | import { Map } from "@react-ol/fiber"; 34 | 35 | export const key = process.env.NEXT_PUBLIC_THUNDERFOREST_KEY; 36 | 37 | export const ExampleXYZ = () => { 38 | return ( 39 | 40 | 41 | 42 | 49 | 50 | 51 | ); 52 | }; 53 | ``` 54 | -------------------------------------------------------------------------------- /packages/website/pages/examples/openlayers/zoomify.en-US.mdx: -------------------------------------------------------------------------------- 1 | # Zoomify 2 | 3 | See https://openlayers.org/en/latest/examples/zoomify.html 4 | 5 | import React, { useState } from "react"; 6 | import "ol/ol.css"; 7 | import { Map } from "@react-ol/fiber"; 8 | 9 | export const ExampleZoomify = () => { 10 | const [imgWidth, imgHeight] = [4000, 3000]; 11 | const zoomifyUrl = "https://ol-zoomify.surge.sh/zoomify/"; 12 | const [pixelRatio, setPixelRatio] = useState("1"); 13 | return ( 14 | <> 15 | 16 | Resolution: 17 | setPixelRatio(e.target.value)} 22 | > 23 | Normal 24 | Retina 25 | 26 | 27 | 28 | 38 | 39 | {/* We have to use the constructor of the object Zoomify because the setUrl method does not do this special behavior: https://github.com/openlayers/openlayers/blob/f3a67e818289282ac71b6d13df96434dd44ace61/src/ol/source/Zoomify.js#L201 */} 40 | 50 | 51 | 52 | > 53 | ); 54 | }; 55 | 56 | 57 | 58 | ```tsx 59 | import React, { useState } from "react"; 60 | import "ol/ol.css"; 61 | import { Map } from "@react-ol/fiber"; 62 | 63 | export const ExampleZoomify = () => { 64 | const [imgWidth, imgHeight] = [4000, 3000]; 65 | const zoomifyUrl = "https://ol-zoomify.surge.sh/zoomify/"; 66 | const [pixelRatio, setPixelRatio] = useState("1"); 67 | return ( 68 | <> 69 | 70 | Resolution: 71 | setPixelRatio(e.target.value)} 75 | > 76 | Normal 77 | Retina 78 | 79 | 80 | 81 | 91 | 92 | {/* We have to use the constructor of the object Zoomify because the setUrl method does not do this special behavior: https://github.com/openlayers/openlayers/blob/f3a67e818289282ac71b6d13df96434dd44ace61/src/ol/source/Zoomify.js#L201 */} 93 | 103 | 104 | 105 | > 106 | ); 107 | }; 108 | ``` 109 | -------------------------------------------------------------------------------- /packages/website/pages/index.en-US.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: React OpenLayers Fiber 3 | --- 4 | 5 | # React OpenLayers Fiber 6 | 7 | `@react-ol/fiber` is a react renderer for OpenLayers https://openlayers.org/ . 8 | 9 | It is based on the same concept as what `@react-three/fiber` does for Three.js. 10 | 11 | ## What does it look like? 12 | 13 | import React from "react"; 14 | import "ol/ol.css"; 15 | import { Map } from "@react-ol/fiber"; 16 | 17 | export const Example = () => ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | 26 | 27 | 28 | ```tsx 29 | import React from "react"; 30 | import "ol/ol.css"; 31 | import { Map } from "@react-ol/fiber"; 32 | 33 | export const Example = () => ( 34 | 35 | 36 | 37 | 38 | 39 | 40 | ); 41 | ``` 42 | 43 | ## Why? 44 | 45 | Build your maps declaratively with re-usable, self-contained components that react to state, are readily interactive and can tap into React's ecosystem. 46 | 47 | ### Does it have limitations? 48 | 49 | None. Everything that works in OpenLayers will work here without exception. 50 | 51 | ### Can it keep up with frequent updates to OpenLayers? 52 | 53 | Yes. There is no hard dependency on a particular OpenLayers version, it does not wrap or duplicate a single OpenLayers class. It merely expresses OpenLayers in JSX: `` becomes `new ol.View()`, and that happens dynamically. 54 | 55 | ### Is it slower than plain OpenLayers? 56 | 57 | No. There is no additional overhead. Components participate in a unified renderloop outside of React. It outperforms OpenLayers at scale due to React's scheduling abilities. 58 | 59 | ## Fundamentals 60 | 61 | You need to be versed in both React and OpenLayers before rushing into this. If you are unsure about React consult the official React docs, especially the section about hooks. As for OpenLayers, make sure you at least glance over the following links: 62 | 63 | - Make sure you have a [basic grasp of OpenLayers](https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html). Keep that site open. 64 | - When you know what a `map` is, a `view`, `layer`, `source`, `geometry`, fork the [demo](/#what-does-it-look-like) above. 65 | - [Look up](https://openlayers.org/en/latest/apidoc/module-ol_layer_Layer-Layer.html) the JSX elements that you see (`layer`, `source`, etc), all OpenLayers exports are native to `@react-ol/fiber`. 66 | - Try changing some values, scroll through our API to see what the various settings and hooks do. 67 | 68 | ## Ecosystem 69 | 70 | - [`@react-three/fiber`](https://github.com/pmndrs/react-three-fiber) – a similar library, targeting Three.js 71 | - [`zustand`](https://github.com/pmndrs/zustand) – state management 72 | - [`react-spring`](https://github.com/pmndrs/react-spring) – a spring-physics-based animation library 73 | -------------------------------------------------------------------------------- /packages/website/pages/meta.en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": { 3 | "title": "Introduction", 4 | "type": "page", 5 | "theme": { 6 | "sidebar": false 7 | } 8 | }, 9 | "docs": { 10 | "title": "Docs", 11 | "type": "page" 12 | }, 13 | "examples": { 14 | "title": "Examples", 15 | "type": "page" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/website/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/website/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crubier/react-openlayers-fiber/8d329539db9217e0974579aa6c998e5f3520ffb6/packages/website/public/favicon.ico -------------------------------------------------------------------------------- /packages/website/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crubier/react-openlayers-fiber/8d329539db9217e0974579aa6c998e5f3520ffb6/packages/website/public/icon.png -------------------------------------------------------------------------------- /packages/website/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /packages/website/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /packages/website/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | "./pages/**/*.{js,ts,jsx,tsx,md,mdx}", 4 | "./components/**/*.{js,ts,jsx,tsx,md,mdx}", 5 | "./theme.config.js", 6 | "./styles.css", 7 | ], 8 | theme: { 9 | extend: {}, 10 | }, 11 | plugins: [ 12 | require('@tailwindcss/forms') 13 | ], 14 | } 15 | -------------------------------------------------------------------------------- /packages/website/theme.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | projectLink: 'https://github.com/crubier/react-openlayers-fiber', // GitHub link in the navbar 3 | docsRepositoryBase: 'https://github.com/crubier/react-openlayers-fiber/blob/master/packages/website/pages', // base URL for the docs repository 4 | titleSuffix: ' – React Openlayers Fiber', 5 | nextLinks: true, 6 | prevLinks: true, 7 | search: true, 8 | customSearch: null, // customizable, you can use algolia for example 9 | darkMode: true, 10 | footer: true, 11 | footerText: `MIT ${new Date().getFullYear()} © Vincent Lecrubier.`, 12 | footerEditLink: `Edit this page on GitHub`, 13 | unstable_flexsearch: true, 14 | floatTOC: true, 15 | logo: ( 16 | <> 17 | React OpenLayers Fiber 18 | > 19 | ), 20 | head: ( 21 | <> 22 | 23 | 24 | 25 | > 26 | ), 27 | } -------------------------------------------------------------------------------- /packages/website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules", ".next"] 20 | } 21 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # React OpenLayers Fiber 2 | 3 | `@react-ol/fiber` is a react renderer for OpenLayers https://openlayers.org/ . 4 | 5 | It is based on the same concept as what `@react-three/fiber` does for Three.js. 6 | 7 | Visit https://react-openlayers-fiber.vercel.app 8 | 9 | ## What does it look like? 10 | 11 | ```tsx 12 | import React from "react"; 13 | import "ol/ol.css"; 14 | import { Map } from "@react-ol/fiber"; 15 | 16 | export const Example = () => ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | ``` 25 | 26 | ## Why? 27 | 28 | Build your maps declaratively with re-usable, self-contained components that react to state, are readily interactive and can tap into React's ecosystem. 29 | 30 | ### Does it have limitations? 31 | 32 | None. Everything that works in OpenLayers will work here without exception. 33 | 34 | ### Can it keep up with frequent updates to OpenLayers? 35 | 36 | Yes. There is no hard dependency on a particular OpenLayers version, it does not wrap or duplicate a single OpenLayers class. It merely expresses OpenLayers in JSX: `` becomes `new ol.View()`, and that happens dynamically. 37 | 38 | ### Is it slower than plain OpenLayers? 39 | 40 | No. There is no additional overhead. Components participate in a unified renderloop outside of React. It outperforms OpenLayers at scale due to React's scheduling abilities. 41 | 42 | ## Fundamentals 43 | 44 | You need to be versed in both React and OpenLayers before rushing into this. If you are unsure about React consult the official React docs, especially the section about hooks. As for OpenLayers, make sure you at least glance over the following links: 45 | 46 | - Make sure you have a [basic grasp of OpenLayers](https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html). Keep that site open. 47 | - When you know what a `map` is, a `view`, `layer`, `source`, `geometry`, fork the [demo](/#what-does-it-look-like) above. 48 | - [Look up](https://openlayers.org/en/latest/apidoc/module-ol_layer_Layer-Layer.html) the JSX elements that you see (`layer`, `source`, etc), all OpenLayers exports are native to `@react-ol/fiber`. 49 | - Try changing some values, scroll through our API to see what the various settings and hooks do. 50 | 51 | ## Ecosystem 52 | 53 | - [`@react-three/fiber`](https://github.com/pmndrs/react-three-fiber) – a similar library, targeting Three.js 54 | - [`zustand`](https://github.com/pmndrs/zustand) – state management 55 | - [`react-spring`](https://github.com/pmndrs/react-spring) – a spring-physics-based animation library 56 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "esModuleInterop": true, 10 | "module": "commonjs", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "react", 15 | "incremental": true 16 | }, 17 | "include": ["**/*.ts", "**/*.tsx"] 18 | // "exclude": ["**/node_modules", "**/dist"] 19 | } 20 | --------------------------------------------------------------------------------
Null Island
You clicked here:
{coordinates && toStringHDMS(toLonLat(coordinates))}