С помощью чего лучше cделать Глобус+карту на веб странице?
Стоит задача:
Вращающийся 3d глобус, с отмеченными на нем метками в виде точек с подписями(например Москва, Англия и т.д.).
Они должны подгружаться из списка в файле cities.
При клике на него (Глобус) он должен трансформироваться в 2d карту с такими же метками
но именно в этой проекции при наведении на метку будет появляться подпись метки (в виде например количество населения и краткого списка архитектурных особенностей).
Так же при клике на метку или же на имя города чтоб перенаправляло на нужную страницу.
С помощью чего это лучше делать?
SVG
Canvas
Ни с Canvas, ни с SVG ранее не работал. Начал изучать canvas но столкнулся с те
что он не сможет обеспечить все что мне нужно. Примеров с глобусом нашел великое множество но нормальной документации не нашел (на нее тоже пните пожалуйста).
Ответы
Ответ 1
Прилагаю Вашему вниманию расширенную версию использования WebWorldWind
Что тут представлено:
Глобус и плоская карта в проекции меркатора.
Звезды, данные взять из json.
Какой-никакой рендеринг атмосферы.
Информация о городах взята из json, нанесены маркеры на карту и добавлены события на клики к ним.
Прикручен анализ того, что под мышкой + по клику фетчим инфу из википедии
use right mouse drag to pan
Вариация предыдущего сниппета, тут все города сложены в kd-дерево, и ищутся в нем ближайшие к месту клика с радиусом в один градус, затем наносятся на карту...
use right mouse drag to pan
Ответ 2
Есть много open source глобусов/карт для WEB, сделанных разными компаниями для разных целей. Перечислю те, с которыми приходилось работать, а вы уж выбирайте:
Cesium
Очень большой по количеству исходного кода и абстракций глобус, он больше ориентирова
на realtime rendering. Очень много чего умеет из коробки, но без знаний компьютерно
графики с ним может оказаться сложновато. Часто используется и можно найти много примеров про него. Хорошо утилизирует ресурсы процессора и видеокарты, в следствие чего его производительность на высоте.
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/world-atlas@1.1.4/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;
}
Mapbox-gl-js
Особенность этой пркекрасной штуки - реалтайм рендеринг векторных тайлов в 2D и 3
режиме(в 3D не глобус, а плоская карта под углом, как columbusview в cesium), но с растровыми источниками данных оно тоже работать умеет. Проекцию толком поменять нельзя.
mapboxgl.accessToken = 'pk.eyJ1Ijoib3dlbmxhbWIiLCJhIjoiY2lleWljcnF4MDBiOXQ0bHR0anRvamtucSJ9.t3YnHHqvQZ8Y0MTCNy0NNw';
this.map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [30.3, 60],
zoom: 11 // starting zoom
});
// Add zoom and rotation controls to the map.
var nav = new mapboxgl.NavigationControl();
map.addControl(nav, 'top-right');
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
Leaflet
Маленькая да удаленькая библиотека для 2D карт. Не использует на прямую видеокарту
и весит очень скромно, при этом имеет большое коммьюнити и множество всяких разных расширений. Хорошо подходит для мобильных карт и слабых компьютеров. Поддерживает различные проекции.
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var osmLayer = new L.TileLayer(osmUrl, {
maxZoom: 18
});
var baseMaps = {"OpenStreetMap": osmLayer};
var map = new L.Map('map', {
center: new L.LatLng(60, 30.3),
zoom: 12,
layers: [osmLayer]
});
body {
margin: 0;
overflow: hidden;
}
#map {
position: fixed;
width: 100%;
height: 100%;
}
Openlayers
Отличная альтернатива leaflet, больше исходного кода, больше фич, больше порог вхождения, много примеров, нормальная поддержка различных проекций. 2D.
new ol.Map({
layers: [new ol.layer.Tile({
source: new ol.source.OSM()
})],
target: 'map',
view: new ol.View({
center: ol.proj.fromLonLat([30.3, 60]),
zoom: 12
})
});
body {
overflow: hidden;
margin: 0;
}
#map {
position: absolute;
height: 100%;
width: 100%;
}
PS: буду дополнять этот пост по мере сил...
UPD 01.01.2019: изменил пример на cesium и 2 картинки на gif
UPD 01.01.2019: изменил пример на worldwind и 1 картинку на gif
Ответ 3
Если нужен именно глобус а не карта, то можно использовать такую связку:
Leaflet + WebGL Earth 2.0, которая расширяет базовые возможности Leaflet и добавляет 3D
Плюсом такого решения является то, что при таком подходе доступны все инструмент
Leaflet. К примеру для работы с метками, их кластеризация, обработка кликов, отображение атрибутивной информации.
Плюс появляется возможность показа в 3D. Поддерживает различные стили от MapBox:
Также есть возможность создавать собственные стили.
Данные могут быть получены с серверов MapBox или с собственных гео-серверов, а также из файлов GeoJson или csv.
Ссылки:
GitHub WebGL Earth 2.0 https://github.com/webglearth/webglearth
Документация и API WebGL Earth 2.0 http://www.webglearth.org
Пример WebGL Earth 2.0 https://www.webglearth.com
Документация API и примеры Leaflet https://leafletjs.com
Ответ 4
Отдельный пост посвященны Cesium.
Тут нанесен topojson с границами субъектов федерации РФ, и города с кластеризацией.
var cities = 'https://raw.githubusercontent.com/lutangar/cities.json/master/cities.json';
var russia = 'https://raw.githubusercontent.com/logvik/d3_russian_map/master/map_assets/russia_mercator.json';
setInitialView(50, 60);
var imagery = Cesium.createDefaultImageryProviderViewModels().slice(3);
var viewer = new Cesium.Viewer("cesiumContainer", {
timeline: false, animation: false, geocoder: false,
imageryProviderViewModels: imagery
});
let ds = new Cesium.CustomDataSource('billboards');
viewer.dataSources.add(ds);
clusteringSupport(ds);
var canvas = createCityImage();
viewer.dataSources.add(Cesium.GeoJsonDataSource.load(russia, {
stroke: Cesium.Color.RED,
fill: Cesium.Color.TRANSPARENT,
strokeWidth: 5,
markerSymbol: '?'
}));
fetch(cities).then(r => r.json())
.then(r => r
.filter((c, i) => c.country === "RU")
// .filter((c, i) => i % 100 === 0)
.forEach(addCity))
addMouseover();
////
var pickedObject;
window.addEventListener('click', function() {
pickedObject&&console.log(pickedObject.id._name)
})
function addMouseover() {
new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
.setInputAction(function(movement) {
pickedObject = viewer.scene.pick(movement.endPosition);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
}
function setInitialView(lon, lat) {
Cesium.Camera.DEFAULT_VIEW_RECTANGLE =
Cesium.Rectangle.fromDegrees(lon-10, lat-10, lon+10, lat+10);
Cesium.Camera.DEFAULT_VIEW_FACTOR = 0;
}
function addCity(city) {
var pos = Cesium.Cartesian3.fromDegrees(+city.lng, +city.lat);
ds.entities.add({
name : city.name,
position: pos,
description: `${city.name}`,
billboard: {
image: canvas
},
label : {
text : city.name,
font : '14pt monospace',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth : 2,
verticalOrigin : Cesium.VerticalOrigin.BOTTOM,
pixelOffset : new Cesium.Cartesian2(0, -9)
}
})
}
function clusteringSupport(ds) {
ds.clustering.enabled = true;
ds.clustering.pixelRange = 35;
var pinBuilder = new Cesium.PinBuilder();
var amounts = [2000, 1000, 500, 100, 50, 10, 9, 8, 7, 6, 5, 4, 3, 2].map(i => ({
i: i, image: pinBuilder.fromText(i + (i>9 ? "+" : ""),
Cesium.Color[i>100?'RED':i>10?'BLUE':'GREEN'], 38).toDataURL()
}));
ds.clustering.clusterEvent.addEventListener(function(clusteredEntities, cluster) {
cluster.label.show = false;
cluster.billboard.show = true;
cluster.billboard.id = cluster.label.id;
cluster.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
for (var i=0; i= amounts[i].i) {
cluster.billboard.image = amounts[i].image;
break;
}
}
});
}
function createCityImage(){
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();
return canvas;
}
.cesium-widget-credits{
display:none !important; /* only for example on ru.so*/
}
PS: фич у этого глобуса достаточно, буду дополнять этот пост иногда...
Комментариев нет:
Отправить комментарий