Example 2: Realtime clusters of tweets. 例2. リアルタイムでツイートのクラスターを作成します。
It's working with Leaflet as an alternative to Leaflet.markercluster. Leaflet.markerclusterの代替としてLeafletと連携しています。
The library is designed for large datasets or live situations. The memory consumption is kept low and the library is fast on mobile devices, thanks to a new algorithm inspired by collision detection in physical engines. このライブラリは、大規模なデータセットやライブの状況を想定して設計されています。物理エンジンの衝突検出にヒントを得た新しいアルゴリズムを採用しているため、メモリ消費量は低く抑えられており、ライブラリはモバイルデバイス上で高速に動作します。
Features 特徴
Realtime リアルタイム
The clusters can be updated in realtime. It's perfect for live datasets or datasets you want to filter at runtime. クラスターをリアルタイムで更新することができます。ライブデータセットや、実行時にフィルタリングしたいデータセットに最適です。
These values are tested with random positions, on a recent laptop, using Chrome 38. One half of markers is moving randomly and the other half is static. It is also fast enough for mobile devices. これらの値は、最近のノートパソコンで、Chrome 38を使用して、ランダムな位置でテストされています。マーカーの半分はランダムに動き、残りの半分は静止しています。モバイル端末でも十分に高速です。
If you prefer real world data, the 50k Leaflet.markercluster example is computed in 60ms (original). 実世界のデータをお望みなら、50k Leaflet.markercluster の例は 60ms (オリジナル) で計算されています。
Weight ウエイト
You can specify the weight of each marker. 各マーカーのウエイトを指定することができます。
For example, you may want to add more importance to a marker representing an incident, than a marker representing a tweet. 例えば、ツイートを表すマーカーよりも、事件を表すマーカーの方がウエイトが高い場合があります。
Categories カテゴリ
You can specify a category for the markers. Then a small object representing the number of markers for each category is attached to the clusters. This way, you can create cluster icons adapted to their content. マーカーのカテゴリを指定することができます。そして、各カテゴリのマーカーの数を表す小さなオブジェクトがクラスタに添付されます。このようにして、その内容に合わせたクラスタアイコンを作成することができます。
Dynamic cluster size 動的なクラスタサイズ
The size of a cluster can be adjusted on the fly (Example) クラスターの大きさをその場で調整可能(例)
Filtering フィルタリング
The markers can be filtered easily with no performance cost. 性能コストをかけずに、簡単にマーカーをろ過することができます。
Usage 使用方法
Classic Way 古典的な方法
<!-- In <head> -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.2.0/dist/leaflet.css"
integrity="sha512-M2wvCLH6DSRazYeZRIm1JnYyh22purTM+FDB5CsyxtQJYeKq83arPe5wgbNmcFXGqiSH2XR8dT/fJISVA1r/zQ=="
crossorigin=""/>
<!-- In <head> or before </body> -->
<script src="https://unpkg.com/leaflet@1.2.0/dist/leaflet.js"
integrity="sha512-lInM/apFSqyy1o6s89K4iQUKg6ppXEgsVxT35HbzUupEVRh2Eu9Wdl4tHj7dZO0s1uvplcYGmt3498TtHq+log=="
crossorigin=""></script>
<script src="PruneCluster/dist/PruneCluster.js"></script>
Webpack & NPM
npm install exports-loader prunecluster
import { PruneCluster, PruneClusterForLeaflet } from 'exports-loader?PruneCluster,PruneClusterForLeaflet!prunecluster/dist/PruneCluster.js'
Example 例
var pruneCluster = new PruneClusterForLeaflet();
...
var marker = new PruneCluster.Marker(59.8717, 11.1909);
pruneCluster.RegisterMarker(marker);
...
leafletMap.addLayer(pruneCluster);
You can specify the size and margin which affect when your clusters and markers will be merged. クラスターとマーカーがマージされるときに影響するサイズとマージンを指定できます。
size defaults to 120 and margin to 20. サイズはデフォルトで120、マージンは20に設定されています。
Update a position ポジションの更新
marker.Move(lat, lng);
Deletions 削除
// Remove all the markers
pruneCluster.RemoveMarkers();
// Remove a list of markers
pruneCluster.RemoveMarkers([markerA,markerB,...]);
Set the category カテゴリを設定する
The category can be a number or a string, but in order to minimize the performance cost, it is recommended to use numbers between 0 and 7. カテゴリは数字でも文字列でも構いませんが、パフォーマンスコストを最小限に抑えるために、0~7の間の数字を使用することをお勧めします。
marker.category = 5;
Set the weight ウエイトを設定する
marker.weight = 4;
Filtering フィルタリング
marker.filtered = true|false;
Set the clustering size クラスタリングサイズを設定する
You can specify a number indicating the area of the cluster. Higher number means more markers "merged". (Example) クラスタの領域を示す数値を指定することができます。数値が大きいほど、より多くのマーカーが "マージ "されていることを意味します。(例)
pruneCluster.Cluster.Size = 87;
Apply the changes 変更を適用する
Must be called when ANY changes are made. 変更が行われたときに呼び出されなければなりません。
pruneCluster.ProcessView();
Add custom data to marker object マーカーオブジェクトにカスタムデータを追加
Each marker has a data object where you can specify your data. 各マーカーには、データを指定できるデータオブジェクトがあります。
Setting up a Leaflet icon or a Leaflet popup リーフレットアイコンやリーフレットのポップアップを設定する
You can attach to the markers an icon object and a popup content マーカーにはアイコンオブジェクトとポップアップコンテンツを添付することができます。
marker.data.icon = L.icon(...); // See http://leafletjs.com/reference.html#icon
marker.data.popup = 'Popup content';
Faster leaflet icons より高速なリーフレットアイコン
If you have a lot of markers, you can create the icons and popups on the fly in order to improve their performance. マーカーが多い場合は、その場でアイコンやポップアップを作成しておくとパフォーマンスが向上します。
You can also override the PreapareLeafletMarker method. You can apply listeners to the markers here. PreapareLeafletMarkerメソッドをオーバーライドすることもできます。ここではマーカーにリスナーを適用することができます。
pruneCluster.PrepareLeafletMarker = function(leafletMarker, data) {
leafletMarker.setIcon(/*... */); // See http://leafletjs.com/reference.html#icon
//listeners can be applied to markers in this function
leafletMarker.on('click', function(){
//do click event logic here
});
// A popup can already be attached to the marker
// bindPopup can override it, but it's faster to update the content instead
if (leafletMarker.getPopup()) {
leafletMarker.setPopupContent(data.name);
} else {
leafletMarker.bindPopup(data.name);
}
};
Setting up a custom cluster icon カスタム クラスター アイコンの設定
pruneCluster.BuildLeafletClusterIcon = function(cluster) {
var population = cluster.population, // the number of markers inside the cluster
stats = cluster.stats; // if you have categories on your markers
// If you want list of markers inside the cluster
// (you must enable the option using PruneCluster.Cluster.ENABLE_MARKERS_LIST = true)
var markers = cluster.GetClusterMarkers()
...
return icon; // L.Icon object (See http://leafletjs.com/reference.html#icon);
};
Listening to events on a cluster クラスタ上のイベントを聞く
To listen to events on the cluster, you will need to override the BuildLeafletCluster method. A click event is already specified on m, but you can add other events like mouseover, mouseout, etc. Any events that a Leaflet marker supports, the cluster also supports, since it is just a modified marker. A full list of events can be found here. クラスタ上のイベントをリッスンするには、BuildLeafletClusterメソッドをオーバーライドする必要があります。クリックイベントはすでに m で指定されていますが、マウスオーバーやマウスアウトなどの他のイベントを追加することができます。リーフレットマーカーがサポートしているイベントはすべて、クラスタもサポートしています。イベントの完全なリストはこちらを参照してください。
Below is an example of how to implement mouseover and mousedown for the cluster, but any events can be used in place of those. 以下にクラスタのマウスオーバーとマウスダウンの実装例を示しますが、これらの代わりに任意のイベントを使用することができます。
pruneCluster.BuildLeafletCluster = function(cluster, position) {
var m = new L.Marker(position, {
icon: pruneCluster.BuildLeafletClusterIcon(cluster)
});
m.on('click', function() {
// Compute the cluster bounds (it's slow : O(n))
var markersArea = pruneCluster.Cluster.FindMarkersInArea(cluster.bounds);
var b = pruneCluster.Cluster.ComputeBounds(markersArea);
if (b) {
var bounds = new L.LatLngBounds(
new L.LatLng(b.minLat, b.maxLng),
new L.LatLng(b.maxLat, b.minLng));
var zoomLevelBefore = pruneCluster._map.getZoom();
var zoomLevelAfter = pruneCluster._map.getBoundsZoom(bounds, false, new L.Point(20, 20, null));
// If the zoom level doesn't change
if (zoomLevelAfter === zoomLevelBefore) {
// Send an event for the LeafletSpiderfier
pruneCluster._map.fire('overlappingmarkers', {
cluster: pruneCluster,
markers: markersArea,
center: m.getLatLng(),
marker: m
});
pruneCluster._map.setView(position, zoomLevelAfter);
}
else {
pruneCluster._map.fitBounds(bounds);
}
}
});
m.on('mouseover', function() {
//do mouseover stuff here
});
m.on('mouseout', function() {
//do mouseout stuff here
});
return m;
};
};
Redraw the icons アイコンを再描画
Marker icon redrawing with a flag: マーカーアイコンを旗で再描画。
This library was developed in context of the BRIDGE project. It is now supported by the community and we thank the contributors. このライブラリはBRIDGEプロジェクトの文脈で開発されました。現在はコミュニティによってサポートされており、貢献者に感謝しています。
Licence ライセンス
The source code of this library is licensed under the MIT License. このライブラリのソースコードはMITライセンスの下でライセンスされています。
//C:\Users\ebata\go_rest\restful\main.go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
// Item representation
type Item struct {
Title string `json:"title"`
Description string `json:"description"`
}
// Global, static list of items
var itemList = []Item{
Item{Title: "Item A", Description: "The first item"},
Item{Title: "Item B", Description: "The second item"},
Item{Title: "Item C", Description: "The third item"},
}
// Controller for the / route (home)
func homePage(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This is the home page. Welcome!")
}
// Contoller for the /items route
func returnAllItems(w http.ResponseWriter, r *http.Request) {
respondWithJson(w, http.StatusOK, itemList)
}
// Controller for the /items/{id} route
func returnSingleItem(w http.ResponseWriter, r *http.Request) {
// Get query parameters using Mux
vars := mux.Vars(r)
// Convert {id} parameter from string to int
key, err := strconv.Atoi(vars["id"])
// If {id} parameter is not valid in
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid reqest payload")
return
}
// If item with ID of {id} does not exist
if key >= len(itemList) {
respondWithError(w, http.StatusNotFound, "Item does not exist")
return
}
respondWithJson(w, http.StatusOK, itemList[key])
}
func respondWithError(w http.ResponseWriter, code int, msg string) {
respondWithJson(w, code, map[string]string{"error": msg})
}
func respondWithJson(w http.ResponseWriter, code int, payload interface{}) {
response, _ := json.Marshal(payload)
w.Header().Set("Content-type", "application/json")
w.WriteHeader(code)
w.Write(response)
}
func handleRequests() {
myRouter := mux.NewRouter().StrictSlash(true)
myRouter.HandleFunc("/", homePage)
myRouter.HandleFunc("/items", returnAllItems)
myRouter.HandleFunc("/items/{id}", returnSingleItem)
log.Fatal(http.ListenAndServe(":8000", myRouter))
}
func main() {
handleRequests()
}
http://localhost:8000/ → ホームページ、テキスト("This is the home page. Welcome!")を見せる
http://localhost:8000/items → "[{"title":"Item A","description":"The first item"},{"title":"Item B","description":"The second item"},{"title":"Item C","description":"The third item"}]"
http://localhost:8000/items/1 → {"title":"Item B","description":"The second item"}
// server.go
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// +build ignore
package main
import (
"flag"
"html/template"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var addr = flag.String("addr", "localhost:8080", "http service address")
var upgrader = websocket.Upgrader{} // use default options
func echo(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
for {
mt, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", message)
err = c.WriteMessage(mt, message)
if err != nil {
log.Println("write:", err)
break
}
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/echo", echo)
http.HandleFunc("/", home)
log.Fatal(http.ListenAndServe(*addr, nil))
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("load", function(evt) {
var output = document.getElementById("output");
var input = document.getElementById("input");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.textContent = message;
output.appendChild(d);
output.scroll(0, output.scrollHeight);
};
document.getElementById("open").onclick = function(evt) {
if (ws) {
return false;
}
ws = new WebSocket("{{.}}");
ws.onopen = function(evt) {
print("OPEN");
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) {
print("RESPONSE: " + evt.data);
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
if (!ws) {
return false;
}
print("SEND: " + input.value);
ws.send(input.value);
return false;
};
document.getElementById("close").onclick = function(evt) {
if (!ws) {
return false;
}
ws.close();
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server,
"Send" to send a message to the server and "Close" to close the connection.
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="input" type="text" value="Hello world!">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output" style="max-height: 70vh;overflow-y: scroll;"></div>
</td></tr></table>
</body>
</html>
`))
// client.go
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// +build ignore
package main
import (
"flag"
"log"
"net/url"
"os"
"os/signal"
"time"
"github.com/gorilla/websocket"
)
var addr = flag.String("addr", "localhost:8080", "http service address")
func main() {
flag.Parse()
log.SetFlags(0)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
log.Printf("connecting to %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
done := make(chan struct{})
go func() {
defer close(done)
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
log.Printf("recv: %s", message)
}
}()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-done:
return
case t := <-ticker.C:
err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
if err != nil {
log.Println("write:", err)
return
}
case <-interrupt:
log.Println("interrupt")
// Cleanly close the connection by sending a close message and then
// waiting (with timeout) for the server to close the connection.
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
log.Println("write close:", err)
return
}
select {
case <-done:
case <-time.After(time.Second):
}
return
}
}
}