Edit in JSFiddle

// ページの読み込みが終わったら、地図表示の処理を行う
window.addEventListener("DOMContentLoaded", ev => { // ■(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, index) => {
        const lonlat = [post.lng, post.lat];
        const feature = new ol.Feature({        // ■(H)-1
            geometry: new ol.geom.Point(ol.proj.transform(lonlat, epsg4326, epsg3857)),
            post: post,         // ■(I)-2
        });
        feature.setId(index);
        return feature;
    });

    // マーカーとツールチップを、店舗情報毎に設定するための Layer
    const marker_layer = new ol.layer.Vector();

    // ol.style.Style のキャッシュ
    // key:
    //  style_${カテゴリ} : マーカー画像を表示するためのスタイル
    //  style_${カテゴリ}_${投稿ID} : ツールチップを表示するためのスタイル
    const style_cache = {};
    function get_style(feature, hover) {
        const post = feature.get("post");
        const key = hover ? `style_tooltip_${feature.getId()}` : `style_${post.cat_id}`;
        let style = style_cache[key];
        if (! style) {
            style = [];
            style.push(new ol.style.Style({
                image: new ol.style.Icon({
                    anchor: [0.5, 1],
                    src: icon_map[post.cat_id],     // ■(I)-1
                }),
            }));
            if (hover) {
                style.push(new ol.style.Style({
                    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,
                    }),
                }));
            }
            style_cache[key] = style;
        }
        return style;
    }

    marker_layer.setSource(new ol.source.Vector({features: features}));  // ■(H)-2
    marker_layer.setStyle((feature, resolution) => get_style(feature));

    const view = new ol.View();

    const map = new ol.Map({        // ■(A)
        target: 'map',          // ■(C)
        view: view,
//      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);
    }

    // 吹き出し
    const popup_e = Object.assign(document.createElement("div"), {  // ■(K)-1
        className: "ol-popup",
        innerHTML: `
            <a href="#" class="ol-popup-closer"></a>
            <div class="ol-popup-content"></div>
        `,
    });
    const popup = new ol.Overlay({
        element: popup_e,
        autoPan: true,
        autoPanAnimation: {
            duration: 250,
        },
    });
    map.addOverlay(popup);
    popup_e.querySelector(".ol-popup-closer").addEventListener("click", ev => {
        popup.setPosition(null);
        ev.target.blur();
        ev.preventDefault();
        ev.stopPropagation();
    });

    map.on("click", evt => {
        // クリックした位置にマーカーがあったら、吹き出しを表示
        const features = map.getFeaturesAtPixel(evt.pixel);
        if (features) {
            const post = features[0].get("post");
            const e = popup.getElement();
            const content = e.querySelector(".ol-popup-content");
            content.innerHTML = `${post.name} : <a href="${post.link}">${post.title}</a>`;
            popup.setPosition(evt.coordinate);

        // マーカー以外のところをクリックしたら、吹き出しを消す
        } else {
            popup.setPosition(null);
        }
    });

    let features_on_mouse = null;
    const map_element = document.getElementById(map.getTarget());
    map.on('pointermove', evt => {
        if (evt.dragging) {
            return;
        }

        // マーカーのツールチップを消す
        if (features_on_mouse) {
            features_on_mouse.forEach(f => f.setStyle(null));
            features_on_mouse = null;
        }

        // マウスポインタがある位置のマーカーに、ツールチップを表示するスタイルを指定する
        const features = map.getFeaturesAtPixel(evt.pixel);
        if (features) {
            features_on_mouse = features;
            features_on_mouse.forEach(f => {
                f.setStyle((feature, resolution) => get_style(feature, true));  // ■(J)-2
            });
        }

        // マーカー上にマウスポインタがあれば、カーソルのスタイルを変更する
        map_element.style.cursor = features ? "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";
}