Skip to content
目录

实现3D内部渐变的行政区效果

例子里我们用到了一个geojson计算bbox的库geojson-bbox

准备数据

datav 地图小工具 这里下载对应的行政区的geojson数据,自己的项目里的数据从哪里下载还是自己生产的有你的项目决定,这里我只是测试使用,故从datav 地图小工具下载了北京的数据

WARNING

地图数据是个很敏感的东西,尤其是国家的行政边界,项目里使用请遵循对应的法律政策,我这里只是测试使用

准备行政区渐变的纹理图片

这里需要用到一个小工具 GeoJSON生成纹理图片工具

  • 上传你的行政区GeoJSON数据

  • 将纹理纹理图片的颜色调成这样子的

    • 外部白色,内部黑色,透明度请根据自己的需要调整
    • 这里图片仅仅作为纹理使用,行政区的颜色我们将交给材质的颜色控制

加载行政区的数据并构造这个平面

js
var planeMaterial = new THREE.MeshLambertMaterial({
    color,
    transparent: true,
    opacity: 0.8,
    side: 0,
});
function addAreaPlane() {
    fetch("../assets/data/beijing.geojson")
        .then((res) => res.json())
        .then((geojson) => {
            // const extent = bbox(geojson);
            // const plane = new AreaPlane(extent, { altitude: 0 }, planeMaterial, threeLayer);
            const extrudePolygons = geojson.features.map((feature) => {
                return threeLayer.toExtrudePolygon(
                    feature,
                    { height: 1 },
                    planeMaterial,
                );
            });
            resetTopUV(extrudePolygons);
            const texture = new THREE.TextureLoader().load(
                "./../assets/image/beijing.png",
                (texture) => {
                    planeMaterial.map = texture;
                    planeMaterial.needsUpdate = true;
                    threeLayer.addMesh(extrudePolygons);
                    // threeLayer.addMesh(plane);
                },
            );
            texture.needsUpdate = true; //使用贴图时进行更新
            texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
            // texture.repeat.set(0.002, 0.002);
            texture.repeat.set(1, 1);
        });
}

重置拔高面的UV

这里默认你懂纹理的uv的知识点,如果你不懂请查阅相关的资料

默认情况下行政区拔高的构造的几何体(Geometry)的纹理坐标值(UV),不是按照这种简单的按照行政区面积比列分布的, 所以需要我们对Geometry的顶面的纹理坐标值进行重计算

具体方法:

  • 求出所有行政区的边界的包围盒

  • 计算每个子区域的坐标点的值换算成在这个包围盒内的占比即可(就是求UV的值,也是对整个纹理图片的划分过程)

  • 更新每个子区域的Geometry的uv值就可以了

WARNING

注意只需要更新下顶面的uv坐标,判断一个顶点的坐标是否是顶面的可以根据顶点的z值来判断

js
function resetTopUV(extrudePolygons) {
    // console.log(geometries);
    //计算所有区域的总的包围盒
    let minx = Infinity,
        miny = Infinity,
        maxx = -Infinity,
        maxy = -Infinity,
        maxZ = -Infinity;
    extrudePolygons.forEach((extrudePolygon) => {
        const geometry = extrudePolygon.getObject3d().geometry;
        const center = extrudePolygon.getObject3d().position;
        const px = center.x,
            py = center.y;
        const position = geometry.attributes.position.array;
        for (let i = 0, len = position.length; i < len; i += 3) {
            const x = position[i] + px,
                y = position[i + 1] + py,
                z = position[i + 2];
            minx = Math.min(minx, x);
            miny = Math.min(miny, y);
            maxx = Math.max(maxx, x);
            maxy = Math.max(maxy, y);
            maxZ = Math.max(maxZ, z);
        }
    });
    console.log(minx, miny, maxx, maxy);
    //计算每个子区域的每个轮廓坐标点的在这个包围盒的百分比
    const dx = maxx - minx,
        dy = maxy - miny;
    extrudePolygons.forEach((extrudePolygon) => {
        const geometry = extrudePolygon.getObject3d().geometry;
        const position = geometry.attributes.position.array;
        const center = extrudePolygon.getObject3d().position;
        const px = center.x,
            py = center.y;
        const uv = geometry.attributes.uv.array;
        let idx = 0;
        for (let i = 0, len = position.length; i < len; i += 3) {
            const x = position[i] + px,
                y = position[i + 1] + py,
                z = position[i + 2];
            if (z === maxZ) {
                const u = (x - minx) / dx;
                const v = (y - miny) / dy;
                const index = idx * 2;
                uv[index] = u;
                uv[index + 1] = v;
            }
            idx++;
        }
    });
}

注意材质需要开启透明选项

构造周边的围墙

js
var material = new THREE.MeshLambertMaterial({ color, transparent: false });

function flatPolygon2Lines(geojson) {
    const results = {
        type: "FeatureCollection",
        features: [],
    };
    geojson.features.forEach((f) => {
        const { geometry, properties } = f;
        const { coordinates, type } = geometry;
        let polygons = [];
        if (type.includes("Multi")) {
            polygons = coordinates;
        } else {
            polygons.push(coordinates);
        }
        polygons.forEach((p) => {
            results.features.push({
                type: "Feature",
                geometry: {
                    type: "MultiLineString",
                    coordinates: p,
                },
                properties,
            });
        });
    });
    return results;
}

function addPolygons() {
    fetch("../assets/data/beijing.geojson")
        .then((res) => res.json())
        .then((geojson) => {
            const geojson1 = flatPolygon2Lines(geojson);
            const lines = maptalks.GeoJSON.toGeometry(geojson1);
            lines.forEach((line) => {
                const extrudeLine = threeLayer.toExtrudeLine(
                    line,
                    { height, altitude: -height, topColor: "#fff" },
                    material,
                );
                threeLayer.addMesh(extrudeLine);
            });
            addOutLines();
        });
}

注意把图形的海拔高度设置为负的目的是不要抬高海平面,方便其他的业务图层数据加到地图,否则会要求其他图层也要设置海拔数据,导致业务逻辑变复杂了,这个样子最简单和不容易出错

添加区域的轮廓线

js
function addOutLines(polygons) {
    fetch("../assets/data/beijingarea.json")
        .then((res) => res.json())
        .then((geojson) => {
            const polygons = maptalks.GeoJSON.toGeometry(geojson);
            polygons.forEach((polygon) => {
                polygon.setSymbol({
                    polygonOpacity: 0,
                    lineWidth: 1,
                    lineColor,
                });
            });
            layer.addGeometry(polygons);
        });
}

TIP

代码里我们把底图(TileLayer)也设置为负值的海拔了,目的也是方便其他的业务图层数据加到地图

js
var map = new maptalks.Map("map", {
    center: [116.63924403, 40.18304877],
    zoom: 9,
    pitch: 60,
    baseLayer: new maptalks.TileLayer("tile", {
        altitude: -height,
        urlTemplate:
            "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
        subdomains: ["a", "b", "c", "d"],
        attribution:
            '&copy; <a href="http://osm.org">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/">CARTO</a>',
    }),
});

This document is generated by mdpress