Illustrates the automatic reprojection of vectors to match the instance's CRS.

Giro3D version
THREE.js version
OpenLayers version
CRS
Memory usage (CPU)
Memory usage (GPU)
Frames
Clear color
Clear alpha
Status
Local clipping enabled
Capabilities
WebGL 2
Max texture units
Max texture size
Precision
Max fragment shader uniforms
Logarithmic depth buffer
Max shader attributes
Check shader errors
EXT_clip_control
EXT_color_buffer_float
EXT_color_buffer_half_float
EXT_conservative_depth
EXT_depth_clamp
EXT_float_blend
EXT_polygon_offset_clamp
EXT_texture_compression_bptc
EXT_texture_compression_rgtc
EXT_texture_filter_anisotropic
EXT_texture_mirror_clamp_to_edge
EXT_texture_norm16
NV_shader_noperspective_interpolation
OES_draw_buffers_indexed
OES_sample_variables
OES_shader_multisample_interpolation
OES_texture_float_linear
OVR_multiview2
WEBGL_clip_cull_distance
WEBGL_compressed_texture_astc
WEBGL_compressed_texture_etc
WEBGL_compressed_texture_etc1
WEBGL_compressed_texture_s3tc
WEBGL_compressed_texture_s3tc_srgb
WEBGL_debug_renderer_info
WEBGL_debug_shaders
WEBGL_lose_context
WEBGL_multi_draw
WEBGL_polygon_mode
WEBGL_stencil_texturing
MSAA
EDL
EDL Radius
EDL Strength
Inpainting
Inpainting steps
Inpainting depth contrib.
Point cloud occlusion
Type
FOV
Automatic plane computation
Far plane
Near plane
Max far plane
Min near plane
Width (pixels)
Height (pixels)
x
y
z
x
y
z
color
Enable cache
Default TTL (seconds)
Capacity (MB)
Capacity (entries)
Entries
Memory usage (approx)
Pending requests
Memory tracker
Identifier
Memory usage (CPU)
Memory usage (GPU)
Status
Render order
Enable
Plane normal X
Plane normal Y
Plane normal Z
Distance
Helper size
Negate plane
Visible
Freeze updates
Opacity
Show volumes
Volume color
Data projection
Wireframe
Materials
Identifier
Memory usage (CPU)
Memory usage (GPU)
Status
Render order
Enable
Plane normal X
Plane normal Y
Plane normal Z
Distance
Helper size
Negate plane
Visible
Freeze updates
Opacity
Show volumes
Volume color
Data projection
Wireframe
Materials
Identifier
Memory usage (CPU)
Memory usage (GPU)
Status
Render order
Enable
Plane normal X
Plane normal Y
Plane normal Z
Distance
Helper size
Negate plane
Visible
Freeze updates
Opacity
Show volumes
Volume color
Data projection
Wireframe
Materials
Show helpers
Show hidden objects
Name filter
Hierarchy
Properties
isObject3D
uuid
name
type
matrixAutoUpdate
matrixWorldAutoUpdate
matrixWorldNeedsUpdate
visible
castShadow
receiveShadow
frustumCulled
renderOrder
x
y
z
x
y
z
Information
?

Some informations embedded in the GeoJSON

    33% © Paris Data

    Display a map in EPGS:2154 with GeoJSON features displayed as meshes in various CRS, showing reprojection capabilities of FeatureCollection.

    index.js
    import { MathUtils } from "three/src/math/MathUtils.js";
    import GeoJSON from "ol/format/GeoJSON.js";
    import VectorSource from "ol/source/Vector.js";
    import { createXYZ } from "ol/tilegrid.js";
    import { tile } from "ol/loadingstrategy.js";
    
    import { Color, Vector3 } from "three";
    import { MapControls } from "three/examples/jsm/controls/MapControls.js";
    
    import Instance from "@giro3d/giro3d/core/Instance.js";
    import Extent from "@giro3d/giro3d/core/geographic/Extent.js";
    import Inspector from "@giro3d/giro3d/gui/Inspector.js";
    import FeatureCollection from "@giro3d/giro3d/entities/FeatureCollection.js";
    import Coordinates from "@giro3d/giro3d/core/geographic/Coordinates";
    
    Instance.registerCRS(
      "EPSG:2154",
      "+proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs",
    );
    
    const extent = new Extent(
      "EPSG:2154",
      -111629.52,
      1275028.84,
      5976033.79,
      7230161.64,
    );
    
    const instance = new Instance({
      target: "view",
      crs: extent.crs,
    });
    
    // This is a GeoJSON with the default crs EPSG:4326
    const arrondissementSource = new VectorSource({
      format: new GeoJSON(),
      url: "./data/paris_arrondissements.geojson",
    });
    
    function getHue(area) {
      const minArea = 991153;
      const maxArea = 16372542;
      const hue = MathUtils.mapLinear(area, minArea, maxArea, 0.2, 0.8);
    
      return MathUtils.clamp(hue, 0, 1);
    }
    
    // Creates the entity. The features will automatically be reprojected before being displayed.
    const arrondissements = new FeatureCollection({
      source: arrondissementSource,
      extent,
      ignoreZ: true,
      minLevel: 0,
      maxLevel: 0,
      style: (feature) => {
        // The style depends on the polygon's area
        const t = getHue(feature.get("surface"));
        const highlight = feature.get("highlight");
        const brightness = highlight ? 1 : 0.7;
        const color = new Color().setHSL(0, t, brightness * t, "srgb");
    
        return {
          fill: {
            color,
            depthTest: false,
            renderOrder: 1,
          },
          stroke: highlight
            ? {
                color: "white",
                depthTest: false,
                renderOrder: 2,
              }
            : null,
        };
      },
    });
    arrondissements.name = "arrondissements";
    instance.add(arrondissements);
    
    // Another GeoJSON in EPSG:3857
    // Although this is non-standard in recent versions of
    // the GeoJSON specification, OpenLayers and Giro3D still
    // support GeoJSON files that have a different CRS than EPSG:4326.
    const perimeterqaaSource = new VectorSource({
      format: new GeoJSON(),
      url: "./data/perimetreqaa.geojson",
    });
    
    const perimeterqaa = new FeatureCollection({
      source: perimeterqaaSource,
      extent,
      ignoreZ: true,
      minLevel: 0,
      maxLevel: 0,
      style: (feature) => {
        const highlight = feature.get("highlight");
        return {
          fill: {
            color: highlight ? "#5d914d" : "#41822d",
            depthTest: false,
            opacity: 0.7,
            renderOrder: 3,
          },
          stroke: {
            color: "#85f516",
            lineWidth: highlight ? 4 : 1,
            depthTest: false,
            renderOrder: 4,
          },
        };
      },
    });
    perimeterqaa.name = "perimeterqaa";
    instance.add(perimeterqaa);
    
    // A WFS source in EPSG:3857
    const bdTopoSource = new VectorSource({
      format: new GeoJSON(),
      url: function url(bbox) {
        return `${
          "https://data.geopf.fr/wfs/ows" +
          "?SERVICE=WFS" +
          "&VERSION=2.0.0" +
          "&request=GetFeature" +
          "&typename=BDTOPO_V3:batiment" +
          "&outputFormat=application/json" +
          "&SRSNAME=EPSG:3857" +
          "&startIndex=0" +
          "&bbox="
        }${bbox.join(",")},EPSG:3857`;
      },
      strategy: tile(createXYZ({ tileSize: 512 })),
    });
    const buildings = new FeatureCollection({
      source: bdTopoSource,
      // we specify that FeatureCollection should reproject the features before displaying them
      dataProjection: "EPSG:3857",
      // We are working on a flat, 2D scene, so we must ignore the Z coordinate of features, if any.
      ignoreZ: true,
      extent,
      style: (feature) => {
        const properties = feature.getProperties();
        const highlighted = properties.highlight;
        let color = "#FFFFFF";
    
        if (highlighted) {
          color = "cyan";
        } else {
          if (properties.usage_1 === "Résidentiel") {
            color = "#9d9484";
          } else if (properties.usage_1 === "Commercial et services") {
            color = "#b0ffa7";
          }
        }
        return {
          fill: {
            color,
            depthTest: false,
            renderOrder: 5,
          },
          stroke: {
            color: "black",
            renderOrder: 6,
            depthTest: false,
          },
        };
      },
      minLevel: 11,
      maxLevel: 11,
    });
    buildings.name = "buildings";
    instance.add(buildings);
    
    const position = new Coordinates("EPSG:2154", 652212.5, 6860754.1, 27717.3);
    const lookAtCoords = new Coordinates("EPSG:2154", 652338.3, 6862087.1, 200);
    const lookAt = new Vector3(lookAtCoords.x, lookAtCoords.y, lookAtCoords.z);
    instance.view.camera.position.set(position.x, position.y, position.z);
    instance.view.camera.lookAt(lookAt);
    
    const controls = new MapControls(instance.view.camera, instance.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.2;
    controls.target.copy(lookAt);
    controls.saveState();
    instance.view.setControls(controls);
    
    // information on click
    const resultTable = document.getElementById("results");
    
    let previousObjects = [];
    const objectsToUpdate = [];
    
    function createResultTable(values) {
      resultTable.innerHTML = "";
    
      for (const value of values) {
        const child = document.createElement("li");
        // child.classList.add('list-group-item');
        child.innerText = value;
        resultTable.appendChild(child);
      }
    }
    
    function pick(e) {
      instance.notifyChange();
      // pick objects
      const pickedObjects = instance.pickObjectsAt(e, {
        radius: 2,
        where: [arrondissements, perimeterqaa],
      });
    
      // Reset highlights
      previousObjects.forEach((o) => o.userData.feature.set("highlight", false));
    
      const tableValues = [];
    
      if (pickedObjects.length !== 0) {
        resultTable.innerHTML = "";
    
        for (const p of pickedObjects) {
          const obj = p.object;
    
          const feature = obj.userData.feature;
          const entity = obj.userData.parentEntity;
    
          objectsToUpdate.push(obj);
    
          if (entity === arrondissements) {
            tableValues.push(feature.get("l_ar"));
          }
          if (entity === perimeterqaa) {
            tableValues.push("Improved Accessibility Zone");
          }
          // highlight it
          feature.set("highlight", true);
        }
      }
    
      createResultTable(tableValues);
    
      instance.notifyChange([...previousObjects, ...objectsToUpdate]);
      previousObjects = [...objectsToUpdate];
      objectsToUpdate.length = 0;
    }
    
    instance.domElement.addEventListener("mousemove", pick);
    
    Inspector.attach("inspector", instance);
    
    index.html
    <!doctype html>
    <html lang="en">
      <head>
        <title>Reprojection of features as mesh</title>
        <meta charset="UTF-8" />
        <meta name="name" content="feature_collection_reprojection" />
        <meta
          name="description"
          content="Illustrates the automatic reprojection of vectors to match the instance&#39;s CRS."
        />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    
        <link rel="icon" href="https://giro3d.org/images/favicon.svg" />
        <link
          href="https://giro3d.org/assets/bootstrap-custom.css"
          rel="stylesheet"
        />
        <script src="https://giro3d.org/assets/bootstrap.bundle.min.js"></script>
        <link
          rel="stylesheet"
          type="text/css"
          href="https://giro3d.org/next/examples/css/example.css"
        />
      </head>
    
      <body>
        <div id="view" class="m-0 p-0 w-100 h-100"></div>
        <div
          id="inspector"
          class="position-absolute top-0 start-0 mh-100 overflow-auto"
        ></div>
    
        <div class="side-pane-with-status-bar" style="min-height: 9rem">
          <div class="card">
            <h5 class="card-header">Information</h5>
    
            <!-- tooltip -->
            <span
              class="badge bg-secondary position-absolute top-0 end-0 m-2"
              data-bs-toggle="popover"
              data-bs-content="tooltip"
              >?</span
            >
    
            <p class="card-text d-none" id="tooltip">
              Some informations embedded in the GeoJSON
            </p>
    
            <div class="card-body pe-none">
              <!-- Result table -->
              <ul style="width: 12rem" id="results"></ul>
            </div>
          </div>
        </div>
    
        <script type="module" src="index.js"></script>
        <script>
          /* activate popovers */
          const popoverTriggerList = [].slice.call(
            document.querySelectorAll('[data-bs-toggle="popover"]'),
          );
          popoverTriggerList.map(
            // bootstrap is used as script in the template, disable warning about undef
            // eslint-disable-next-line no-undef
            (popoverTriggerEl) =>
              new bootstrap.Popover(popoverTriggerEl, {
                trigger: "hover",
                placement: "left",
                content: document.getElementById(
                  popoverTriggerEl.getAttribute("data-bs-content"),
                ).innerHTML,
                html: true,
              }),
          );
        </script>
      </body>
    </html>
    
    package.json
    {
        "name": "feature_collection_reprojection",
        "dependencies": {
            "@giro3d/giro3d": "git+https://gitlab.com/giro3d/giro3d.git"
        },
        "devDependencies": {
            "vite": "^3.2.3"
        },
        "scripts": {
            "start": "vite",
            "build": "vite build"
        }
    }