-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[@sigma/layer-leaflet] WIP - first working version
- Loading branch information
Showing
14 changed files
with
322,797 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.gitignore | ||
node_modules | ||
src | ||
tsconfig.json |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
"name": "@sigma/layer-leaflet", | ||
"version": "3.0.0-beta.13", | ||
"description": "A plugin to set a geographical map in background", | ||
"main": "dist/sigma-layer-leaflet.cjs.js", | ||
"module": "dist/sigma-layer-leaflet.esm.js", | ||
"types": "dist/sigma-layer-leaflet.cjs.d.ts", | ||
"files": [ | ||
"/dist" | ||
], | ||
"sideEffects": false, | ||
"homepage": "https://www.sigmajs.org", | ||
"bugs": "http://github.com/jacomyal/sigma.js/issues", | ||
"repository": { | ||
"type": "git", | ||
"url": "http://github.com/jacomyal/sigma.js.git" | ||
}, | ||
"keywords": [ | ||
"graph", | ||
"graphology", | ||
"sigma" | ||
], | ||
"license": "MIT", | ||
"preconstruct": { | ||
"entrypoints": [ | ||
"index.ts" | ||
] | ||
}, | ||
"peerDependencies": { | ||
"leaflet": "^1.9.4", | ||
"sigma": ">=3.0.0-beta.10" | ||
}, | ||
"exports": { | ||
".": { | ||
"module": "./dist/sigma-layer-leaflet.esm.js", | ||
"import": "./dist/sigma-layer-leaflet.cjs.mjs", | ||
"default": "./dist/sigma-layer-leaflet.cjs.js" | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
"devDependencies": { | ||
"@types/leaflet": "^1.9.12" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import Graph from "graphology"; | ||
import { Attributes } from "graphology-types"; | ||
import L, { LatLngBounds, Map } from "leaflet"; | ||
import { Sigma } from "sigma"; | ||
import { DEFAULT_SETTINGS } from "sigma/settings"; | ||
|
||
import { graphToLatlng, latlngToGraph } from "./utils"; | ||
|
||
/** | ||
* Synchronise the sigma BBOX with the leaflet one. | ||
*/ | ||
function syncLeafletBboxWithGraph(sigma: Sigma, map: Map): void { | ||
const viewportDimensions = sigma.getDimensions(); | ||
|
||
// Graph BBOX | ||
const graphBottomLeft = sigma.viewportToGraph({ x: 0, y: viewportDimensions.height }, { padding: 0 }); | ||
const graphTopRight = sigma.viewportToGraph({ x: viewportDimensions.width, y: 0 }, { padding: 0 }); | ||
|
||
// Geo BBOX | ||
const geoSouthWest = graphToLatlng(map, graphBottomLeft); | ||
const geoNorthEast = graphToLatlng(map, graphTopRight); | ||
|
||
// Set map BBOX | ||
const bounds = new LatLngBounds(geoSouthWest, geoNorthEast); | ||
map.flyToBounds(bounds, { animate: false }); | ||
|
||
// Handle side effects when bounds have some "void" area on top or bottom of the map | ||
// When it happens, flyToBound don't really do its job and there is a translation of the graph that match the void height. | ||
// So we have to do a pan in pixel... | ||
const worldSize = map.getPixelWorldBounds().getSize(); | ||
const mapBottomY = map.getPixelBounds().getBottomLeft().y; | ||
const mapTopY = map.getPixelBounds().getTopRight().y; | ||
const panVector: [number, number] = [0, 0]; | ||
if (mapTopY < 0) panVector[1] = mapTopY; | ||
if (mapBottomY > worldSize.y) panVector[1] = mapBottomY - worldSize.y; | ||
if (panVector[1] !== 0) { | ||
map.panBy(panVector, { animate: false }); | ||
} | ||
} | ||
|
||
/** | ||
* On the graph, we store the 2D projection of the geographical lat/long. | ||
*/ | ||
export function bindLeafletLayer( | ||
sigma: Sigma, | ||
opts?: { | ||
tileLayer?: { urlTemplate: string; attribution?: string }; | ||
getNodeLatLng?: (nodeAttributes: Attributes) => { lat: number; lng: number }; | ||
}, | ||
) { | ||
// Map initialization | ||
// ~~~~~~~~~~~~~~~ | ||
// Creating map container for leaflet | ||
const mapContainer = document.createElement("div"); | ||
const mapContainerId = `${sigma.getContainer().id}-map`; | ||
mapContainer.setAttribute("id", mapContainerId); | ||
mapContainer.setAttribute("style", "position: relative; top:0; left:0; width: 100%; height:100%; z-index:-1"); | ||
sigma.getContainer().appendChild(mapContainer); | ||
// Initialize the map | ||
const map = L.map(mapContainerId, { | ||
zoomControl: false, | ||
zoomSnap: 0, | ||
zoom: 0, | ||
}).setView([0, 0], 0); | ||
let tileUrl = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"; | ||
let tileAttribution: string | undefined = | ||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'; | ||
if (opts?.tileLayer) { | ||
tileUrl = opts.tileLayer.urlTemplate; | ||
tileAttribution = opts.tileLayer.attribution; | ||
} | ||
L.tileLayer(tileUrl, { attribution: tileAttribution }).addTo(map); | ||
|
||
// Graph mutation for geo | ||
// ~~~~~~~~~~~~~~~~~~~~~~ | ||
// Mute the given graph by generating the sigma x,y coords by taking the geo coordinates | ||
// and project them in the 2D space of the map | ||
function updateGraphCoordinates(graph: Graph) { | ||
// const japan = latlngToGraph(map, { lat: 0, lng: 138 }); | ||
// console.log(japan); | ||
graph.updateEachNodeAttributes((_node, attrs) => { | ||
const coords = latlngToGraph( | ||
map, | ||
opts?.getNodeLatLng ? opts.getNodeLatLng(attrs) : { lat: attrs.lat, lng: attrs.lng }, | ||
); | ||
return { | ||
...attrs, | ||
x: coords.x, | ||
y: coords.y, | ||
}; | ||
}); | ||
} | ||
// Update the sigma graph for geopspatial coords | ||
updateGraphCoordinates(sigma.getGraph()); | ||
|
||
// Sigma configuration & lifecycle | ||
// ~~~~~~~~~~~~~~~ | ||
// `stagePadding: 0` is mandatory, so the bbox of the map & Sigma is the same. | ||
sigma.setSetting("stagePadding", 0); | ||
// Sync the graph BBOX with the leaflet one after each render. | ||
function fnAfterRender() { | ||
syncLeafletBboxWithGraph(sigma, map); | ||
} | ||
sigma.on("afterRender", fnAfterRender); | ||
|
||
// Settings the max camera ratio to avoid side effect | ||
// NB: Need to wait the first render of the map | ||
setTimeout(() => { | ||
const worldPixelSize = map.getPixelWorldBounds().getSize(); | ||
const mapPixelSize = map.getPixelBounds().getSize(); | ||
sigma.setSetting("maxCameraRatio", worldPixelSize.y / mapPixelSize.y); | ||
}, 1000); | ||
|
||
// Clean up function to remove everything | ||
function clean() { | ||
map.remove(); | ||
mapContainer.remove(); | ||
sigma.off("afterRender", fnAfterRender); | ||
sigma.setSetting("stagePadding", DEFAULT_SETTINGS.stagePadding); | ||
} | ||
// When sigma is killed, do the cleanup | ||
sigma.on("kill", clean); | ||
|
||
return { | ||
clean, | ||
map, | ||
updateGraphCoordinates, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { Map } from "leaflet"; | ||
|
||
/** | ||
* Given a geo point returns its graph coords. | ||
*/ | ||
export function latlngToGraph(map: Map, coord: { lat: number; lng: number }): { x: number; y: number } { | ||
const data = map.project({ lat: coord.lat, lng: coord.lng }, 0); | ||
return { | ||
x: data.x, | ||
// Y are reversed between geo / sigma | ||
y: map.getContainer().clientHeight - data.y, | ||
}; | ||
} | ||
|
||
/** | ||
* Given a graph coords returns it's lat/lng coords. | ||
*/ | ||
export function graphToLatlng(map: Map, coords: { x: number; y: number }): { lat: number; lng: number } { | ||
const data = map.unproject([coords.x, map.getContainer().clientHeight - coords.y], 0); | ||
return { lat: data.lat, lng: data.lng }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "ESNext", // Specifies the JavaScript version to target when transpiling code. | ||
"useDefineForClassFields": true, // Enables the use of 'define' for class fields. | ||
"lib": ["ES2020", "DOM", "DOM.Iterable"], // Specifies the libraries available for the code. | ||
"module": "ESNext", // Defines the module system to use for code generation. | ||
"skipLibCheck": true, // Skips type checking of declaration files. | ||
|
||
/* Bundler mode */ | ||
"moduleResolution": "node", // Specifies how modules are resolved when bundling. | ||
"allowSyntheticDefaultImports": true, | ||
"allowImportingTsExtensions": true, // Allows importing TypeScript files with extensions. | ||
"resolveJsonModule": true, // Enables importing JSON modules. | ||
"isolatedModules": true, // Ensures each file is treated as a separate module. | ||
"noEmit": true, // Prevents TypeScript from emitting output files. | ||
|
||
/* Linting */ | ||
"strict": true, // Enables strict type checking. | ||
"noUnusedLocals": true, // Flags unused local variables. | ||
"noUnusedParameters": true, // Flags unused function parameters. | ||
"noFallthroughCasesInSwitch": true, // Requires handling all cases in a switch statement. | ||
"declaration": true // Generates declaration files for TypeScript. | ||
}, | ||
"include": ["src"], // Specifies the directory to include when searching for TypeScript files. | ||
"exclude": ["src/**/__docs__", "src/**/__test__"] | ||
} |
Oops, something went wrong.