Polygon Triangulation For Globe
Is it real to fill all polygons? Codepen. As I get it ThreeGeoJSON can not fill polygons, outlines only. Also I've tried Earcut for triangulation. drawThreeGeo(data, radius, 'spher
Solution 1:
I suggest you to use better map: countries.geojson
The solution consists of following steps, for each shape:
- Put vertices inside of shape, so that when triangulated, it could bend around the globe,
 - Run https://github.com/mapbox/delaunator to build triangulated mesh,
 - Step 2 will create triangles outside the shape too, we need to remove them by looking into each triangle, and deciding if it belongs to shape or not,
 - Bend the triangulated mesh with 
convertCoordinates 
You can test my jsfiddle: http://jsfiddle.net/mmalex/pg5a4132/
Warning: it is quite slow because of high level of detail of input.
The complete solution:
/* Draw GeoJSON
Iterates through the latitude and longitude values, converts the values to XYZ coordinates, and draws the geoJSON geometries.
*/letTRIANGULATION_DENSITY = 5; // make it smaller for more dense meshfunctionverts2array(coords) {
    let flat = [];
    for (let k = 0; k < coords.length; k++) {
        flat.push(coords[k][0], coords[k][1]);
    }
    return flat;
}
functionarray2verts(arr) {
    let coords = [];
    for (let k = 0; k < arr.length; k += 2) {
        coords.push([arr[k], arr[k + 1]]);
    }
    return coords;
}
functionfindBBox(points) {
    let min = {
        x: 1e99,
        y: 1e99
    };
    let max = {
        x: -1e99,
        y: -1e99
    };
    for (var point_num = 0; point_num < points.length; point_num++) {
        if (points[point_num][0] < min.x) {
            min.x = points[point_num][0];
        }
        if (points[point_num][0] > max.x) {
            max.x = points[point_num][0];
        }
        if (points[point_num][1] < min.y) {
            min.y = points[point_num][1];
        }
        if (points[point_num][1] > max.y) {
            max.y = points[point_num][1];
        }
    }
    return {
        min: min,
        max: max
    };
}
functionisInside(point, vs) {
    // ray-casting algorithm based on// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.htmlvar x = point[0],
        y = point[1];
    var inside = false;
    for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
        var xi = vs[i][0],
            yi = vs[i][1];
        var xj = vs[j][0],
            yj = vs[j][1];
        var intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
        if (intersect) inside = !inside;
    }
    return inside;
}
functiongenInnerVerts(points) {
    let res = [];
    for (let k = 0; k < points.length; k++) {
        res.push(points[k]);
    }
    let bbox = findBBox(points);
    let step = TRIANGULATION_DENSITY;
    let k = 0;
    for (let x = bbox.min.x + step / 2; x < bbox.max.x; x += step) {
        for (let y = bbox.min.y + step / 2; y < bbox.max.y; y += step) {
            let newp = [x, y];
            if (isInside(newp, points)) {
                res.push(newp);
            }
            k++;
        }
    }
    return res;
}
functionremoveOuterTriangles(delaunator, points) {
    let newTriangles = [];
    for (let k = 0; k < delaunator.triangles.length; k += 3) {
        let t0 = delaunator.triangles[k];
        let t1 = delaunator.triangles[k + 1];
        let t2 = delaunator.triangles[k + 2];
        let x0 = delaunator.coords[2 * t0];
        let y0 = delaunator.coords[2 * t0 + 1];
        let x1 = delaunator.coords[2 * t1];
        let y1 = delaunator.coords[2 * t1 + 1];
        let x2 = delaunator.coords[2 * t2];
        let y2 = delaunator.coords[2 * t2 + 1];
        let midx = (x0 + x1 + x2) / 3;
        let midy = (y0 + y1 + y2) / 3;
        let midp = [midx, midy];
        if (isInside(midp, points)) {
            newTriangles.push(t0, t1, t2);
        }
    }
    delaunator.triangles = newTriangles;
}
var x_values = [];
var y_values = [];
var z_values = [];
var progressEl = $("#progress");
var clickableObjects = [];
var someColors = [0x909090, 0x808080, 0xa0a0a0, 0x929292, 0x858585, 0xa9a9a9];
functiondrawThreeGeo(json, radius, shape, options) {
    var json_geom = createGeometryArray(json);
    var convertCoordinates = getConversionFunctionName(shape);
    for (var geom_num = 0; geom_num < json_geom.length; geom_num++) {
        console.log("Processing " + geom_num + " of " + json_geom.length + " shapes");
        // if (geom_num !== 17) continue;// if (geom_num > 10) break;if (json_geom[geom_num].type == 'Point') {
            convertCoordinates(json_geom[geom_num].coordinates, radius);
            drawParticle(y_values[0], z_values[0], x_values[0], options);
        } elseif (json_geom[geom_num].type == 'MultiPoint') {
            for (let point_num = 0; point_num < json_geom[geom_num].coordinates.length; point_num++) {
                convertCoordinates(json_geom[geom_num].coordinates[point_num], radius);
                drawParticle(y_values[0], z_values[0], x_values[0], options);
            }
        } elseif (json_geom[geom_num].type == 'LineString') {
            for (let point_num = 0; point_num < json_geom[geom_num].coordinates.length; point_num++) {
                convertCoordinates(json_geom[geom_num].coordinates[point_num], radius);
            }
            drawLine(y_values, z_values, x_values, options);
        } elseif (json_geom[geom_num].type == 'Polygon') {
            let group = createGroup(geom_num);
            let randomColor = someColors[Math.floor(someColors.length * Math.random())];
            for (let segment_num = 0; segment_num < json_geom[geom_num].coordinates.length; segment_num++) {
                let coords = json_geom[geom_num].coordinates[segment_num];
                let refined = genInnerVerts(coords);
                let flat = verts2array(refined);
                let d = newDelaunator(flat);
                removeOuterTriangles(d, coords);
                let delaunayVerts = array2verts(d.coords);
                for (let point_num = 0; point_num < delaunayVerts.length; point_num++) {
                    // convertCoordinates(refined[point_num], radius);convertCoordinates(delaunayVerts[point_num], radius);
                }
                // drawLine(y_values, z_values, x_values, options);drawMesh(group, y_values, z_values, x_values, d.triangles, randomColor);
            }
        } elseif (json_geom[geom_num].type == 'MultiLineString') {
            for (let segment_num = 0; segment_num < json_geom[geom_num].coordinates.length; segment_num++) {
                let coords = json_geom[geom_num].coordinates[segment_num];
                for (let point_num = 0; point_num < coords.length; point_num++) {
                    convertCoordinates(json_geom[geom_num].coordinates[segment_num][point_num], radius);
                }
                drawLine(y_values, z_values, x_values);
            }
        } elseif (json_geom[geom_num].type == 'MultiPolygon') {
            let group = createGroup(geom_num);
            let randomColor = someColors[Math.floor(someColors.length * Math.random())];
            for (let polygon_num = 0; polygon_num < json_geom[geom_num].coordinates.length; polygon_num++) {
                for (let segment_num = 0; segment_num < json_geom[geom_num].coordinates[polygon_num].length; segment_num++) {
                    let coords = json_geom[geom_num].coordinates[polygon_num][segment_num];
                    let refined = genInnerVerts(coords);
                    let flat = verts2array(refined);
                    let d = newDelaunator(flat);
                    removeOuterTriangles(d, coords);
                    let delaunayVerts = array2verts(d.coords);
                    for (let point_num = 0; point_num < delaunayVerts.length; point_num++) {
                        // convertCoordinates(refined[point_num], radius);convertCoordinates(delaunayVerts[point_num], radius);
                    }
                    // drawLine(y_values, z_values, x_values, options);drawMesh(group, y_values, z_values, x_values, d.triangles, randomColor)
                }
            }
        } else {
            thrownewError('The geoJSON is not valid.');
        }
    }
    progressEl.text("Complete!");
}
functioncreateGeometryArray(json) {
    var geometry_array = [];
    if (json.type == 'Feature') {
        geometry_array.push(json.geometry);
    } elseif (json.type == 'FeatureCollection') {
        for (var feature_num = 0; feature_num < json.features.length; feature_num++) {
            geometry_array.push(json.features[feature_num].geometry);
        }
    } elseif (json.type == 'GeometryCollection') {
        for (var geom_num = 0; geom_num < json.geometries.length; geom_num++) {
            geometry_array.push(json.geometries[geom_num]);
        }
    } else {
        thrownewError('The geoJSON is not valid.');
    }
    //alert(geometry_array.length);return geometry_array;
}
functiongetConversionFunctionName(shape) {
    var conversionFunctionName;
    if (shape == 'sphere') {
        conversionFunctionName = convertToSphereCoords;
    } elseif (shape == 'plane') {
        conversionFunctionName = convertToPlaneCoords;
    } else {
        thrownewError('The shape that you specified is not valid.');
    }
    return conversionFunctionName;
}
functionconvertToSphereCoords(coordinates_array, sphere_radius) {
    var lon = coordinates_array[0];
    var lat = coordinates_array[1];
    x_values.push(Math.cos(lat * Math.PI / 180) * Math.cos(lon * Math.PI / 180) * sphere_radius);
    y_values.push(Math.cos(lat * Math.PI / 180) * Math.sin(lon * Math.PI / 180) * sphere_radius);
    z_values.push(Math.sin(lat * Math.PI / 180) * sphere_radius);
}
functionconvertToPlaneCoords(coordinates_array, radius) {
    var lon = coordinates_array[0];
    var lat = coordinates_array[1];
    var plane_offset = radius / 2;
    z_values.push((lat / 180) * radius);
    y_values.push((lon / 180) * radius);
}
functiondrawParticle(x, y, z, options) {
    var particle_geom = newTHREE.Geometry();
    particle_geom.vertices.push(newTHREE.Vector3(x, y, z));
    var particle_material = newTHREE.ParticleSystemMaterial(options);
    var particle = newTHREE.ParticleSystem(particle_geom, particle_material);
    scene.add(particle);
    clearArrays();
}
functiondrawLine(x_values, y_values, z_values, options) {
    var line_geom = newTHREE.Geometry();
    createVertexForEachPoint(line_geom, x_values, y_values, z_values);
    var line_material = newTHREE.LineBasicMaterial(options);
    var line = newTHREE.Line(line_geom, line_material);
    scene.add(line);
    clearArrays();
}
functioncreateGroup(idx) {
    var group = newTHREE.Group();
    group.userData.userText = "_" + idx;
    scene.add(group);
    return group;
}
functiondrawMesh(group, x_values, y_values, z_values, triangles, color) {
    var geometry = newTHREE.Geometry();
    for (let k = 0; k < x_values.length; k++) {
        geometry.vertices.push(
            newTHREE.Vector3(x_values[k], y_values[k], z_values[k])
        );
    }
    for (let k = 0; k < triangles.length; k += 3) {
        geometry.faces.push(newTHREE.Face3(triangles[k], triangles[k + 1], triangles[k + 2]));
    }
    geometry.computeVertexNormals()
    var mesh = newTHREE.Mesh(geometry, newTHREE.MeshLambertMaterial({
        side: THREE.DoubleSide,
        color: color,
        wireframe: true
    }));
    clickableObjects.push(mesh);
    group.add(mesh);
    clearArrays();
}
functioncreateVertexForEachPoint(object_geometry, values_axis1, values_axis2, values_axis3) {
    for (var i = 0; i < values_axis1.length; i++) {
        object_geometry.vertices.push(newTHREE.Vector3(values_axis1[i],
            values_axis2[i], values_axis3[i]));
    }
}
functionclearArrays() {
    x_values.length = 0;
    y_values.length = 0;
    z_values.length = 0;
}
var scene = newTHREE.Scene();
var raycaster = newTHREE.Raycaster();
var camera = newTHREE.PerspectiveCamera(32, window.innerWidth / window.innerHeight, 0.5, 1000);
var radius = 200;
camera.position.x = 140.7744005681177;
camera.position.y = 160.30950538100814;
camera.position.z = 131.8637122564268;
var renderer = newTHREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
var light = newTHREE.HemisphereLight(0xffffbb, 0x080820, 1);
scene.add(light);
var light = newTHREE.AmbientLight(0x505050); // soft white light
scene.add(light);
var geometry = newTHREE.SphereGeometry(radius, 32, 32);
var material = newTHREE.MeshPhongMaterial({
    color: 0x1e90ff
});
var sphere = newTHREE.Mesh(geometry, material);
scene.add(sphere);
var test_json = $.getJSON("https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson", function(data) {
    drawThreeGeo(data, radius + 1, 'sphere', {
        color: 'yellow'
    })
});
var controls = newTHREE.TrackballControls(camera);
controls.rotateSpeed *= 0.5;
controls.zoomSpeed *= 0.5;
controls.panSpeed *= 0.5;
controls.minDistance = 10;
controls.maxDistance = 5000;
functionrender() {
    controls.update();
    requestAnimationFrame(render);
    renderer.setClearColor(0x1e90ff, 1);
    renderer.render(scene, camera);
}
render()
functionconvert_lat_lng(lat, lng, radius) {
    var phi = (90 - lat) * Math.PI / 180,
        theta = (180 - lng) * Math.PI / 180,
        position = newTHREE.Vector3();
    position.x = radius * Math.sin(phi) * Math.cos(theta);
    position.y = radius * Math.cos(phi);
    position.z = radius * Math.sin(phi) * Math.sin(theta);
    return position;
}
// this will be 2D coordinates of the current mouse position, [0,0] is middle of the screen.var mouse = newTHREE.Vector2();
var hoveredObj; // this objects is hovered at the moment// Following two functions will convert mouse coordinates// from screen to three.js system (where [0,0] is in the middle of the screen)functionupdateMouseCoords(event, coordsObj) {
    coordsObj.x = ((event.clientX - renderer.domElement.offsetLeft + 0.5) / window.innerWidth) * 2 - 1;
    coordsObj.y = -((event.clientY - renderer.domElement.offsetTop + 0.5) / window.innerHeight) * 2 + 1;
}
functiononMouseMove(event) {
    updateMouseCoords(event, mouse);
    latestMouseProjection = undefined;
    clickedObj = undefined;
    raycaster.setFromCamera(mouse, camera); {
        var intersects = raycaster.intersectObjects(clickableObjects);
        let setGroupColor = function(group, colorHex) {
            for (let i = 0; i < group.children.length; i++) {
                if (!group.children[i].userData.color) {
                    group.children[i].userData.color = hoveredObj.parent.children[i].material.color.clone();
                    group.children[i].material.color.set(colorHex);
                    group.children[i].material.needsUpdate = true;
                }
            }
        }
        let resetGroupColor = function(group) {
            // set all shapes of the group to initial colorfor (let i = 0; i < group.children.length; i++) {
                if (group.children[i].userData.color) {
                    group.children[i].material.color = group.children[i].userData.color;
                    delete group.children[i].userData.color;
                    group.children[i].material.needsUpdate = true;
                }
            }
        }
        if (intersects.length > 0) {
            latestMouseProjection = intersects[0].point;
            // reset colors for previously hovered groupif (hoveredObj) {
                resetGroupColor(hoveredObj.parent);
            }
            hoveredObj = intersects[0].object;
            if (!hoveredObj.parent) return;
            // set colors for hovered groupsetGroupColor(hoveredObj.parent, 0xff0000);
        } else {
            if (!hoveredObj || !hoveredObj.parent) return;
            // nothing is hovered => just reset colors on the last groupresetGroupColor(hoveredObj.parent);
            hoveredObj = undefined;
            console.log("<deselected>");
        }
    }
}
window.addEventListener('mousemove', onMouseMove, false);
Solution 2:
You'd need to split each country into a separate geometry, use a raycaster to find out which country the mouse is over, then change its material.color. You can see raycasting in action in this example with source code available on the bottom-right corner. The key lines in that example are:
function onDocumentMouseMove(event) {
    event.preventDefault();
    mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
    mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
function render() {
    // find intersections
    raycaster.setFromCamera( mouse, camera );
    var intersects = raycaster.intersectObjects( scene.children );
    if ( intersects.length > 0 ) {
        if ( INTERSECTED != intersects[ 0 ].object ) {
            if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
            INTERSECTED = intersects[ 0 ].object;
            INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
            INTERSECTED.material.emissive.setHex( 0xff0000 );
        }
    } else {
        if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
        INTERSECTED = null;
    }
    renderer.render( scene, camera );
}



Post a Comment for "Polygon Triangulation For Globe"