С помощью чего лучше cделать Глобус+карту на веб странице?

1,00
р.
Стоит задача:
Вращающийся 3d глобус, с отмеченными на нем метками в виде точек с подписями(например Москва, Англия и т.д.). Они должны подгружаться из списка в файле cities. При клике на него (Глобус) он должен трансформироваться в 2d карту с такими же метками, но именно в этой проекции при наведении на метку будет появляться подпись метки (в виде например количество населения и краткого списка архитектурных особенностей). Так же при клике на метку или же на имя города чтоб перенаправляло на нужную страницу.
С помощью чего это лучше делать?
SVG Canvas
Ни с Canvas, ни с SVG ранее не работал. Начал изучать canvas но столкнулся с тем что он не сможет обеспечить все что мне нужно. Примеров с глобусом нашел великое множество но нормальной документации не нашел (на нее тоже пните пожалуйста).

Ответ
Есть много open source глобусов/карт для WEB, сделанных разными компаниями для разных целей. Перечислю те, с которыми приходилось работать, а вы уж выбирайте:

Cesium
Очень большой по количеству исходного кода и абстракций глобус, он больше ориентирован на realtime rendering. Очень много чего умеет из коробки, но без знаний компьютерной графики с ним может оказаться сложновато. Часто используется и можно найти много примеров про него. Хорошо утилизирует ресурсы процессора и видеокарты, в следствие чего его производительность на высоте.



2d/3d var extent = Cesium.Rectangle.fromDegrees(20.3,50,40.3,70) Cesium.Camera.DEFAULT_VIEW_RECTANGLE = extent Cesium.Camera.DEFAULT_VIEW_FACTOR = 0 var viewer = new Cesium.CesiumWidget('cesiumContainer',{ imageryProvider : Cesium.createOpenStreetMapImageryProvider({ url: 'https://a.tile.openstreetmap.org/' }), }) var scene = viewer.scene var billboards = scene.primitives.add(new Cesium.BillboardCollection()) var canvas = document.createElement('canvas') canvas.width = 16 canvas.height = 16 var ctx = canvas.getContext('2d') ctx.beginPath() ctx.arc(5, 5, 5, 0, Cesium.Math.TWO_PI, true) ctx.closePath() ctx.fillStyle = 'red' ctx.fill() billboards.add({ image: canvas, position: Cesium.Cartesian3.fromDegrees(30.3,60) }) var labels = new Cesium.LabelCollection() labels.add({ position: Cesium.Cartesian3.fromDegrees(30.3,60), text: 'Saint-Petersburg', font: '15px Helvetica', fillColor: new Cesium.Color(0, 0, 0), }) scene.primitives.add(labels) let is2d document.querySelector('button').onclick = () => { scene.mode = (is2d = !is2d) ? Cesium.SceneMode.SCENE2D : Cesium.SceneMode.SCENE3D }


Web World Wind
Тоже глобус, намного проще внутри, помедленнее работает, чем Cesium, меньше реализованных фишек, легче поддаётся пониманию и кастомизации. Работает в один поток, вычисления на видеокарте не ускоряет, почти не использует шейдеры.



let c = document.getElementById('canvasOne') c.width = window.innerWidth c.height = window.innerHeight var wwd = new WorldWind.WorldWindow("canvasOne") wwd.addLayer(new WorldWind.AtmosphereLayer()) wwd.addLayer(new WorldWind.BingAerialWithLabelsLayer()) var markers = new WorldWind.RenderableLayer("Markers") wwd.addLayer(markers) var attributes = new WorldWind.PlacemarkAttributes() var position = new WorldWind.Location(60, 30) var placemark = new WorldWind.Placemark(position, /*eyeDistanceScaling*/true, attributes) attributes.imageOffset = new WorldWind.Offset( WorldWind.OFFSET_FRACTION, 0.3, WorldWind.OFFSET_FRACTION, 0.0) attributes.labelAttributes.color = WorldWind.Color.YELLOW attributes.labelAttributes.offset = new WorldWind.Offset( WorldWind.OFFSET_FRACTION, 0.5, WorldWind.OFFSET_FRACTION, 1.0) placemark.label = "Saint-Petersburg" placemark.altitudeMode = WorldWind.CLAMP_TO_GROUND placemark.eyeDistanceScalingThreshold = 2500000 attributes.imageSource = WorldWind.configuration.baseUrl + "images/pushpins/plain-red.png" markers.addRenderable(placemark) body { margin:0 overflow:hidden }


D3.js
Очень мощная штука, больше подходит для SVG, но и по-другому можно отрендерить, содержит математику для множества географических проекций и их расширения, отлично подходит для создания 2D карт и карт во всяких экстравагантных проекциях, отличных от mercator/wgs84, которые используются почти во всех других картографических движках.



var width = 750, height = 500 var options = [ {name: "Aitoff", projection: d3.geoAitoff()}, {name: "Albers", projection: d3.geoAlbers().scale(145).parallels([20, 50])}, {name: "August", projection: d3.geoAugust().scale(60)}, {name: "Baker", projection: d3.geoBaker().scale(100)}, {name: "Boggs", projection: d3.geoBoggs()}, {name: "Bonne", projection: d3.geoBonne().scale(120)}, {name: "Bromley", projection: d3.geoBromley()}, {name: "Collignon", projection: d3.geoCollignon().scale(93)}, {name: "Craster Parabolic", projection: d3.geoCraster()}, {name: "Eckert I", projection: d3.geoEckert1().scale(165)}, {name: "Eckert II", projection: d3.geoEckert2().scale(165)}, {name: "Eckert III", projection: d3.geoEckert3().scale(180)}, {name: "Eckert IV", projection: d3.geoEckert4().scale(180)}, {name: "Eckert V", projection: d3.geoEckert5().scale(170)}, {name: "Eckert VI", projection: d3.geoEckert6().scale(170)}, {name: "Eisenlohr", projection: d3.geoEisenlohr().scale(60)}, {name: "Equirectangular (Plate Carrée)", projection: d3.geoEquirectangular()}, {name: "Hammer", projection: d3.geoHammer().scale(165)}, {name: "Hill", projection: d3.geoHill()}, {name: "Goode Homolosine", projection: d3.geoHomolosine()}, {name: "Kavrayskiy VII", projection: d3.geoKavrayskiy7()}, {name: "Lambert cylindrical equal-area", projection: d3.geoCylindricalEqualArea()}, {name: "Lagrange", projection: d3.geoLagrange().scale(120)}, {name: "Larrivée", projection: d3.geoLarrivee().scale(95)}, {name: "Laskowski", projection: d3.geoLaskowski().scale(120)}, {name: "Loximuthal", projection: d3.geoLoximuthal()}, // {name: "Mercator", projection: d3.geoMercator().scale(490 / 2 / Math.PI)}, {name: "Miller", projection: d3.geoMiller().scale(100)}, {name: "McBryde–Thomas Flat-Polar Parabolic", projection: d3.geoMtFlatPolarParabolic()}, {name: "McBryde–Thomas Flat-Polar Quartic", projection: d3.geoMtFlatPolarQuartic()}, {name: "McBryde–Thomas Flat-Polar Sinusoidal", projection: d3.geoMtFlatPolarSinusoidal()}, {name: "Mollweide", projection: d3.geoMollweide().scale(165)}, {name: "Natural Earth", projection: d3.geoNaturalEarth()}, {name: "Nell–Hammer", projection: d3.geoNellHammer()}, {name: "Polyconic", projection: d3.geoPolyconic().scale(100)}, {name: "Robinson", projection: d3.geoRobinson()}, {name: "Sinusoidal", projection: d3.geoSinusoidal()}, {name: "Sinu-Mollweide", projection: d3.geoSinuMollweide()}, {name: "van der Grinten", projection: d3.geoVanDerGrinten().scale(75)}, {name: "van der Grinten IV", projection: d3.geoVanDerGrinten4().scale(120)}, {name: "Wagner IV", projection: d3.geoWagner4()}, {name: "Wagner VI", projection: d3.geoWagner6()}, {name: "Wagner VII", projection: d3.geoWagner7()}, {name: "Winkel Tripel", projection: d3.geoWinkel3()} ] options.forEach(function(o) { o.projection.rotate([0, 0]).center([0, 0]) }) var i = 0, n = options.length - 1 var projection = options[i].projection var path = d3.geoPath(projection) var graticule = d3.geoGraticule() var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) svg.append("defs").append("path") .datum({type: "Sphere"}) .attr("id", "sphere") .attr("d", path) svg.append("use") .attr("class", "stroke") .attr("xlink:href", "#sphere") svg.append("use") .attr("class", "fill") .attr("xlink:href", "#sphere") svg.append("path") .datum(graticule) .attr("class", "graticule") .attr("d", path) d3.json("https://unpkg.com/[email protected]/world/110m.json").then(function(world) { svg.insert("path", ".graticule") .datum(topojson.feature(world, world.objects.land)) .attr("class", "land") .attr("d", path) }) var menu = d3.select("#projection-menu") .on("change", change) menu.selectAll("option") .data(options) .enter().append("option") .text(function(d) { return d.name }) update(options[0]) function loop() { var j = Math.floor(Math.random() * n) menu.property("selectedIndex", i = j + (j >= i)) update(options[i]) } function change() { clearInterval(interval) update(options[this.selectedIndex]) } function update(option) { svg.selectAll("path").interrupt().transition() .duration(1000).ease(d3.easeLinear) .attrTween("d", projectionTween(projection, projection = option.projection)) d3.timeout(loop, 1000) } function projectionTween(projection0, projection1) { return function(d) { var t = 0 var projection = d3.geoProjection(project) .scale(1) .translate([width / 2, height / 2]) var path = d3.geoPath(projection) function project(λ, φ) { λ *= 180 / Math.PI, φ *= 180 / Math.PI var p0 = projection0([λ, φ]), p1 = projection1([λ, φ]) return [(1 - t) * p0[0] + t * p1[0], (1 - t) * -p0[1] + t * -p1[1]] } return function(_) { t = _ return path(d) } } } body { background: #fcfcfa height: 500px position: relative width: 960px } #projection-menu { position: absolute right: 10px top: 10px } .stroke { fill: none stroke: #000 stroke-width: 3px } .fill { fill: #fff } .graticule { fill: none stroke: #777 stroke-width: .5px stroke-opacity: .5 } .land { fill: #222 } .boundary { fill: none stroke: #fff stroke-width: .5px }