Страницы

Поиск по вопросам

вторник, 26 ноября 2019 г.

С помощью чего лучше 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: фич у этого глобуса достаточно, буду дополнять этот пост иногда...

Комментариев нет:

Отправить комментарий