1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Bobと拡張ノードで外部の地図に座標地点を描画させる(SPSS Modeler データ加工逆引き10-31)

1
Posted at

店舗の数だけファイルを分割するpython拡張ノードをBobに作らせる

スクリーンショット 2026-05-08 16.31.16.png

1.想定される利用目的

・店舗と特定の顧客/車載器/スマートフォンの位置を表示させる

2.サンプルストリームとデータのダウンロード

ストリーム

3.サンプルストリームの説明

スクリーンショット 2026-05-08 16.31.54.png

a.入力するデータは以下の通りです。1000種類の地点のダミーデータです。

スクリーンショット 2026-05-08 16.33.10.png

b.[データ型]ノードを編集します。

スクリーンショット 2026-05-08 16.34.19.png

c.[拡張のエクスポート]ノードを編集します。スクリプトをどのようにBobに書かせたかは後述します。
出力するパスを変更して利用します。

スクリーンショット 2026-05-08 16.36.42.png

[実行]します。任意の場所にhtmlファイルが生成されますので展開します。

スクリーンショット 2026-05-08 16.35.42.png

スクリーンショット 2026-05-08 16.36.04.png

Bobによるスクリプトの作成手順

1.Bobを起動して、作業フォルダを指定します。

スクリーンショット 2026-03-31 11.13.04.png

2.SPSS Modeler[拡張のエクスポート]ノードからサンプル構文をコピーします。

スクリーンショット 2026-03-31 11.14.40.png

3.プロンプトを作成します。サンプル構文をペーストします。

プロンプト

Data.xlsxを読み込み
SPSS Modelerの拡張のエクスポートノードで座標の地点をOpenStreetMapに表示するするネイティブPython APIスクリプトを書いてください
結果はHTMLファイルにして

/Users/aa517821/Desktop

に保存してください


- 拡張のエクスポートノードのスクリプトの解説として
@https://www.ibm.com/docs/ja/spss-modeler/18.6.0?topic=spark-native-python-apis を参考にしてください
- シバンは不要です

###Use this code to import data from Modeler to Python
#import modelerpy
#modelerData = modelerpy.readPandasDataframe()
#modelerDataModel = modelerpy.getDataModel()

4.プロンプトを実行します。

スクリーンショット 2026-03-31 11.17.21.png

5.Bobの以下の出力を[拡張のエクスポート]にそのまま利用します。

Pythonスクリプト

# SPSS Modeler 拡張エクスポートノード用スクリプト
# OpenStreetMapで座標データを表示するHTMLファイルを生成

import pandas as pd
import json
from datetime import datetime

# Modelerからデータを読み込む
# 注: 実際のSPSS Modeler環境では以下のコメントを解除してください
# import modelerpy
# modelerData = modelerpy.readPandasDataframe()
# modelerDataModel = modelerpy.getDataModel()

# テスト用: data.xlsxから直接読み込み(実際の環境では上記のmodelerpy使用)
try:
    modelerData = pd.read_excel('data.xlsx')
except:
    # Modeler環境での実行を想定
    import modelerpy
    modelerData = modelerpy.readPandasDataframe()
    modelerDataModel = modelerpy.getDataModel()

# データの検証
if 'latitude' not in modelerData.columns or 'longitude' not in modelerData.columns:
    raise ValueError("データに'latitude''longitude'列が必要です")

# 海上・河川判定関数
def is_on_land(lat, lng):
    """陸地上のポイントかどうかを判定"""
    # 東京湾の範囲
    if lat < 35.45 and lng > 139.7 and lng < 140.0:
        return False
    if lat < 35.55 and lng > 139.75 and lng < 139.95:
        return False
    
    # 隅田川周辺
    if lng > 139.78 and lng < 139.81 and lat > 35.63 and lat < 35.73:
        return False
    
    # 荒川周辺
    if lng > 139.78 and lng < 139.85 and lat > 35.68 and lat < 35.78:
        return False
    
    # 多摩川河口付近
    if lat < 35.55 and lng > 139.70 and lng < 139.75:
        return False
    
    return True

# 陸地上のデータのみフィルタリング
filtered_data = modelerData[modelerData.apply(
    lambda row: is_on_land(row['latitude'], row['longitude']), axis=1
)]

# データをJSON形式に変換
map_data = filtered_data.to_dict('records')
map_data_json = json.dumps(map_data, ensure_ascii=False, indent=2)

# 虎ノ門の座標
toranomon_lat = 35.6676
toranomon_lng = 139.7502

# HTMLファイルの内容を生成
html_content = f'''<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SPSS Modeler - 座標データマップ</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
    <script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css" />
    <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css" />
    <style>
        * {{
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }}
        body {{
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            overflow: hidden;
        }}
        #map {{
            height: 100vh;
            width: 100%;
        }}
        #info {{
            position: absolute;
            top: 10px;
            right: 10px;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            z-index: 1000;
            min-width: 250px;
        }}
        #info h3 {{
            margin: 0 0 15px 0;
            color: #333;
            font-size: 18px;
        }}
        #info p {{
            margin: 8px 0;
            color: #666;
            font-size: 14px;
        }}
        #info .count {{
            font-size: 36px;
            font-weight: bold;
            color: #0066CC;
            margin: 10px 0;
            text-align: center;
        }}
        #info .label {{
            font-size: 12px;
            color: #999;
            text-align: center;
            margin-bottom: 15px;
        }}
        #info .timestamp {{
            font-size: 11px;
            color: #999;
            margin-top: 10px;
            padding-top: 10px;
            border-top: 1px solid #eee;
        }}
        #controls {{
            position: absolute;
            top: 10px;
            left: 10px;
            background: white;
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            z-index: 1000;
        }}
        #controls button {{
            background: #0066CC;
            color: white;
            border: none;
            padding: 12px 20px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 14px;
            margin: 5px 0;
            width: 100%;
            transition: background 0.3s;
            font-weight: 500;
        }}
        #controls button:hover {{
            background: #0052A3;
        }}
        #controls button:active {{
            background: #003D7A;
            transform: scale(0.98);
        }}
        .leaflet-popup-content {{
            margin: 12px;
            min-width: 200px;
        }}
        .leaflet-popup-content h4 {{
            margin: 0 0 12px 0;
            color: #333;
            border-bottom: 2px solid #0066CC;
            padding-bottom: 8px;
            font-size: 16px;
        }}
        .leaflet-popup-content p {{
            margin: 6px 0;
            color: #555;
            font-size: 14px;
        }}
        .leaflet-popup-content strong {{
            color: #333;
            font-weight: 600;
        }}
        .marker-cluster-small {{
            background-color: rgba(0, 102, 204, 0.6);
        }}
        .marker-cluster-small div {{
            background-color: rgba(0, 102, 204, 0.8);
        }}
        .marker-cluster-medium {{
            background-color: rgba(0, 102, 204, 0.6);
        }}
        .marker-cluster-medium div {{
            background-color: rgba(0, 102, 204, 0.8);
        }}
        .marker-cluster-large {{
            background-color: rgba(0, 102, 204, 0.6);
        }}
        .marker-cluster-large div {{
            background-color: rgba(0, 102, 204, 0.8);
        }}
    </style>
</head>
<body>
    <div id="controls">
        <button onclick="toggleClustering()">🔄 クラスタリング切替</button>
        <button onclick="fitAllMarkers()">🗺️ 全体表示</button>
    </div>
    
    <div id="info">
        <h3>📍 SPSS Modeler データ</h3>
        <div class="count" id="count">{len(filtered_data)}</div>
        <div class="label">地点</div>
        <p style="font-size: 12px; line-height: 1.5;">
            <small>✓ マーカークリックで詳細表示<br>
            ✓ ズームでクラスタ展開<br>
            ✓ ドラッグで地図移動<br>
            ✓ 海上・河川上を除外</small>
        </p>
        <div class="timestamp">
            生成日時: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
        </div>
    </div>
    
    <div id="map"></div>

    <script>
        // データを埋め込み
        const mapData = {map_data_json};
        
        // マップを初期化(東京中心)
        const map = L.map('map').setView([35.6762, 139.6503], 11);

        // CartoDB Positronタイルレイヤーを追加
        L.tileLayer('https://{{s}}.basemaps.cartocdn.com/light_all/{{z}}/{{x}}/{{y}}{{r}}.png', {{
            attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>',
            subdomains: 'abcd',
            maxZoom: 20
        }}).addTo(map);

        // マーカークラスタグループを作成
        let markerClusterGroup = L.markerClusterGroup({{
            chunkedLoading: true,
            chunkInterval: 200,
            chunkDelay: 50,
            maxClusterRadius: 50,
            spiderfyOnMaxZoom: true,
            showCoverageOnHover: false,
            zoomToBoundsOnClick: true,
            iconCreateFunction: function(cluster) {{
                const count = cluster.getChildCount();
                let size = 'small';
                if (count > 100) size = 'large';
                else if (count > 50) size = 'medium';
                
                return L.divIcon({{
                    html: '<div><span>' + count + '</span></div>',
                    className: 'marker-cluster marker-cluster-' + size,
                    iconSize: L.point(40, 40)
                }});
            }}
        }});

        let markers = [];
        let allBounds = null;
        let clusteringEnabled = true;

        // 虎ノ門の座標
        const targetLat = {toranomon_lat};
        const targetLng = {toranomon_lng};

        // マーカーを作成
        function createMarkers(data) {{
            const markerArray = [];
            
            data.forEach((item) => {{
                const lat = parseFloat(item.latitude);
                const lng = parseFloat(item.longitude);
                
                if (isNaN(lat) || isNaN(lng)) {{
                    console.warn(`無効な座標: ID ${{item.ID}}`);
                    return;
                }}
                
                // 虎ノ門の座標に最も近いかチェック(距離0.001度以内)
                const distance = Math.sqrt(Math.pow(lat - targetLat, 2) + Math.pow(lng - targetLng, 2));
                const isTarget = distance < 0.001;
                
                // 円形マーカーを作成
                const marker = L.circleMarker([lat, lng], {{
                    radius: isTarget ? 10 : 6,
                    fillColor: isTarget ? '#0000FF' : '#FF0000',
                    color: '#FFFFFF',
                    weight: isTarget ? 3 : 2,
                    opacity: 1,
                    fillOpacity: isTarget ? 0.9 : 0.7
                }});

                // ポップアップを設定
                marker.bindPopup(`
                    <div>
                        <h4>📍 データポイント #${{item.ID}}</h4>
                        <p><strong>ID:</strong> ${{item.ID}}</p>
                        <p><strong>緯度:</strong> ${{lat.toFixed(6)}}</p>
                        <p><strong>経度:</strong> ${{lng.toFixed(6)}}</p>
                    </div>
                `);

                markers.push(marker);
                markerArray.push([lat, lng]);
            }});

            // マーカークラスタグループに追加
            markerClusterGroup.addLayers(markers);
            map.addLayer(markerClusterGroup);

            // 件数を表示
            document.getElementById('count').textContent = data.length.toLocaleString();

            // すべてのマーカーの境界を計算
            if (markerArray.length > 0) {{
                allBounds = L.latLngBounds(markerArray);
                map.fitBounds(allBounds, {{ padding: [50, 50] }});
            }}

            console.log(`✓ ${{data.length}}件のデータポイントを表示しました`);
        }}

        // クラスタリングの切り替え
        function toggleClustering() {{
            if (clusteringEnabled) {{
                // クラスタリングを無効化
                map.removeLayer(markerClusterGroup);
                markers.forEach(marker => marker.addTo(map));
                clusteringEnabled = false;
                console.log('クラスタリング: OFF');
            }} else {{
                // クラスタリングを有効化
                markers.forEach(marker => map.removeLayer(marker));
                map.addLayer(markerClusterGroup);
                clusteringEnabled = true;
                console.log('クラスタリング: ON');
            }}
        }}

        // 全体表示
        function fitAllMarkers() {{
            if (allBounds) {{
                map.fitBounds(allBounds, {{ padding: [50, 50] }});
            }}
        }}

        // データを読み込んで表示
        if (mapData && mapData.length > 0) {{
            createMarkers(mapData);
        }} else {{
            console.error('データが見つかりません');
            alert('エラー: データが読み込まれていません');
        }}
    </script>
</body>
</html>'''

# HTMLファイルを保存
output_path = '/Users/aa517821/Desktop/spss_modeler_map_output.html'
with open(output_path, 'w', encoding='utf-8') as f:
    f.write(html_content)

print(f"✓ HTMLファイルを生成しました: {output_path}")
print(f"✓ 表示データ数: {len(filtered_data)}件(元データ: {len(modelerData)}件)")
print(f"✓ 除外データ数: {len(modelerData) - len(filtered_data)}件(海上・河川上)")

# SPSS Modeler環境では、データモデルを設定
# 注: 実際のSPSS Modeler環境では以下のコメントを解除してください
# modelerpy.setOutputDataModel(modelerDataModel)

# Made with Bob



注意事項

別のプロンプトを偶然引き継ぎ、海上座標へのプロットを回避しているようですが
実際には有効になっていません。

4.参考情報

店舗の数だけファイルを分割するpython拡張ノードをBobに作らせる

Modelerスクリプトでループ処理を実行する

SPSS Modeler ノードリファレンス目次

SPSS Modeler 逆引きストリーム集(データ加工)

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?