// ページの読み込みが終わったら、地図表示の処理を行う window.addEventListener("DOMContentLoaded", function () { // ■(B) const posts = JSON.parse(document.getElementById("data").innerHTML); // カテゴリーID とマーカー画像の対応 const icon_map = { 2: "http://dev.openlayers.org/img/marker.png", 3: "http://dev.openlayers.org/img/marker-blue.png", 4: "http://dev.openlayers.org/img/marker-gold.png", 5: "http://dev.openlayers.org/img/marker-green.png", }; const epsg4326 = ol.proj.get('EPSG:4326'); const epsg3857 = ol.proj.get('EPSG:3857'); // マーカー(ol.Feature)の生成 const features = posts.map(post => { const lonlat = [post.lng, post.lat]; return new ol.Feature({ // ■(H)-1 geometry: new ol.geom.Point(ol.proj.transform(lonlat, epsg4326, epsg3857)), post: post, // ■(I)-2 }); }); // マーカーとツールチップを、店舗情報毎に設定するための Layer const marker_layer = new ol.layer.Vector(); marker_layer.setSource(new ol.source.Vector({features: features})); // ■(H)-2 marker_layer.setStyle(function(feature, resolution) { const post = feature.get("post"); return [new ol.style.Style({ image: new ol.style.Icon({ anchor: [0.5, 1], src: icon_map[post.cat_id], // ■(I)-1 }), })]; }); const view = new ol.View(); const map = new ol.Map({ // ■(A) target: 'map', // ■(C) view: view, // renderer: "webgl", // interactions: ol.interaction.defaults() }); map.addLayer(new ol.layer.Tile({ source: new ol.source.OSM() })); // ■(A) map.addLayer(marker_layer); // ■(H)-3 map.addControl(new ol.control.ScaleLine()); // ■(E) // マーカーが含まれる範囲の Extent と、それに合わせたズーミング let ext = ol.extent.boundingExtent(posts.map(p => [p.lng, p.lat])); // ■(F)-2 ext = ol.proj.transformExtent(ext, epsg4326, epsg3857); view.fit(ext, map.getSize()); // ■(D)、(F)-1 // ズームの最大値は 16 まで // ■(G) if (view.getZoom() > 16) { view.setZoom(16); } let popup_z_index = 9000; // 吹き出しの表示、非表示を切り替える関数 function toggle_popup(feature, coordinate) { let popup = feature.get("popup"); if (! popup) { const post = feature.get("post"); const e = Object.assign(document.createElement("div"), { // ■(K)-1 className: "ol-popup", innerHTML: ` <a href="#" class="ol-popup-closer"></a> <div class="ol-popup-content"> ${post.name} : <a href="${post.link}">${post.title}</a> </div> `, ol_feature: feature, }); // addOverlay() は、.ol-overlay-container の先頭に insert していくので、 // 後で表示した方を上に出すように、z-index を調整する e.style.zIndex = ++popup_z_index; popup = new ol.Overlay({ element: e, autoPan: true, autoPanAnimation: { duration: 250, }, }); feature.set("popup", popup); e.querySelector(".ol-popup-closer").addEventListener("click", ev => { toggle_popup(ev.target.parentNode.ol_feature); ev.target.blur(); ev.preventDefault(); ev.stopPropagation(); }); map.addOverlay(popup); popup.setPosition(feature.getGeometry().getFirstCoordinate()); popup.setPosition(coordinate); } else { map.removeOverlay(popup); feature.unset("popup"); } } map.on("click", evt => { // クリックした位置にマーカーがあったら、吹き出しの表示、非表示を切り替える const features = map.getFeaturesAtPixel(evt.pixel); // ■(L)-1 if (features) { toggle_popup(features[0], evt.coordinate); } }); // マーカーにツールチップを表示するためのスタイル const tooltipStyle = function(feature, resolution) { const post = feature.get("post"); return new ol.style.Style({ image: new ol.style.Icon({ anchor: [0.5, 1], src: icon_map[post.cat_id], // ■(I)-1 }), text: new ol.style.Text({ // ■(J)-1 text: post.title, backgroundFill: new ol.style.Fill({ color: "white", }), padding: [2,2,2,2], textAlign: "left", offsetX: 10, offsetY: -30, }), }); }; map.on('pointermove', function(evt) { if (evt.dragging) { return; } // マーカーのツールチップを消す marker_layer.getSource().getFeatures().forEach(f => { f.setStyle(null); }); // マウスポインタがある位置のマーカーに、ツールチップを表示するスタイルを指定する const pixel = map.getEventPixel(evt.originalEvent); const feature = map.forEachFeatureAtPixel(pixel, function(feature) { // ■(J)-2 feature.setStyle(tooltipStyle); return feature; }); // マーカー上にマウスポインタがあれば、カーソルのスタイルを変更する const target = document.getElementById(map.getTarget()); target.style.cursor = feature ? "pointer" : ""; }); });
<div id="map" style="width: 99%; height: 650px;"></div> <script id="data" type="text/json"> [ { "lat": 35.701418, "lng": 139.74787, "name": "和食料理店", "link": "http://www.google.com/search?q=%e5%92%8c%e9%a3%9f%e5%89%b2%e7%83%b9zen", "title": "和食割烹ZEN", "cat_id": "2" }, { "lat": 35.6950886, "lng": 139.74937320000004, "name": "犬猫病院", "link": "http://www.google.com/search?q=%e3%82%a2%e3%83%8b%e3%83%9e%e3%83%ab%e3%83%9b%e3%82%b9%e3%83%94%e3%82%bf%e3%83%ab", "title": "アニマルホスピタル", "cat_id": "5" }, { "lat": 35.67694669999999, "lng": 139.7635034, "name": "カフェ", "link": "http://www.google.com/search?q=%e3%82%b9%e3%82%a4%ef%bc%8d%e3%83%84%e3%83%99%e3%83%aa%ef%bc%8d", "title": "スイ-ツベリ-", "cat_id": "2" }, { "lat": 35.6964531, "lng": 139.77445769999997, "name": "服屋", "link": "http://www.google.com/search?q=%e3%83%a6%e3%83%8b%e3%82%b7%e3%83%ad", "title": "ユニシロ", "cat_id": "4" }, { "lat": 35.6964946, "lng": 139.77230780000002, "name": "不動産屋", "link": "http://www.google.com/search?q=%e3%82%a8%e3%82%b9%e3%83%86%ef%bc%8d%e3%83%88%e6%9d%b1%e4%ba%ac", "title": "エステ-ト東京", "cat_id": "3" }, { "lat": 35.6945504, "lng": 139.770258, "name": "たこ焼き屋", "link": "http://www.google.com/search?q=%e3%81%8f%e3%82%8b%e3%81%8f%e3%82%8b", "title": "くるくる", "cat_id": "2" }, { "lat": 35.6963474, "lng": 139.77209949999997, "name": "ペット", "link": "http://www.google.com/search?q=%e3%83%9a%e3%83%83%e3%83%88%e3%82%b7%e3%83%a7%e3%83%83%e3%83%97%e3%82%8f%e3%82%93%e3%81%ab%e3%82%83%e3%82%93", "title": "ペットショップわんにゃん", "cat_id": "5" }, { "lat": 35.6950947, "lng": 139.7685136, "name": "工務店", "link": "http://www.google.com/search?q=%e3%83%88%e3%83%b3%e3%82%ab%e3%83%81%e5%b7%a5%e5%8b%99%e5%ba%97", "title": "トンカチ工務店", "cat_id": "3" }, { "lat": 35.6971917, "lng": 139.76922739999998, "name": "お好み焼き", "link": "http://www.google.com/search?q=%e3%81%98%e3%82%85%e3%81%86%e3%81%98%e3%82%85%e3%81%86", "title": "じゅうじゅう", "cat_id": "2" }, { "lat": 35.6749887, "lng": 139.76436450000006, "name": "美容室", "link": "http://www.google.com/search?q=%e3%82%b5%e3%83%a9%e3%82%b5%e3%83%a9%e7%be%8e%e5%ae%b9%e5%ae%a4", "title": "サラサラキュ-ティクル", "cat_id": "4" }, { "lat": 35.6817879, "lng": 139.76676139999995, "name": "パン屋", "link": "http://www.google.com/search?q=%e3%83%91%e3%83%b3%e5%b1%8b%e3%82%a4%ef%bc%8d%e3%82%b9%e3%83%88", "title": "イ-ストパン", "cat_id": "2" } ] </script>
#map { /* (N) */ width: 99%; height: 650px; } /* ■(K)-2 */ .ol-popup { position: absolute; background-color: white; -webkit-filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2)); filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2)); padding: 15px; border-radius: 10px; border: 1px solid #cccccc; bottom: 12px; left: -50px; min-width: 280px; } .ol-popup:after, .ol-popup:before { top: 100%; border: solid transparent; content: " "; height: 0; width: 0; position: absolute; pointer-events: none; } .ol-popup:after { border-top-color: white; border-width: 10px; left: 48px; margin-left: -10px; } .ol-popup:before { border-top-color: #cccccc; border-width: 11px; left: 48px; margin-left: -11px; } .ol-popup-closer { text-decoration: none; position: absolute; top: 2px; right: 8px; } .ol-popup-closer:after { content: "\2716"; }