chromaDB + streamlit + elyza:jp8b

import streamlit as st
from openai import OpenAI
import chromadb
from docx import Document
import requests


# ChromaDBの設定
DB_DIR = "./chroma_db"
chroma_client = chromadb.PersistentClient(path=DB_DIR)

if "collection" not in st.session_state:
    st.session_state.collection = chroma_client.get_or_create_collection(
        name="local_docs"
    )

# Ollamaからインストールしたモデルを使ったベクトル化関数
def ollama_embed(text):
    r = requests.post(
        "http://localhost:12000/api/embeddings",
        json={"model": "nomic-embed-text", "prompt": text}
    )
    data = r.json()
    return data["embedding"]

# Wordファイルを読み込む関数
def load_word_document(file):
    return "\n".join(para.text for para in Document(file).paragraphs)

# テキスト分割関数
def split_text(text):
    chunk_size = 200
    overlap = 50
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunks.append(text[start:end])
        start += chunk_size - overlap
    return chunks

## サイドバーの設定 ##
st.set_page_config(page_title="Local LLM Chat")

st.sidebar.title("設定")
model = st.sidebar.text_input("モデル名", value="llama3.1:8b")
temperature = st.sidebar.slider("temperature", 0.0, 2.0, 0.3, 0.1)
system_prompt = st.sidebar.text_area(
    "System Prompt",
    "あなたは有能なアシスタントです。日本語で回答してください",
)

# ワードファイルのアップロード
uploaded_files = st.sidebar.file_uploader(
    "Wordファイルをアップロード(.docx)",
    type=["docx"],
    accept_multiple_files=True
)

if st.sidebar.button("インデックス作成"):
    for file in uploaded_files:
        text = load_word_document(file)
        chunks = split_text(text)
        for i, chunk in enumerate(chunks):
            embed = ollama_embed(chunk)
            st.session_state.collection.add(
                documents=[chunk],
                embeddings=,
                ids=[f"{file.name}_{i}"]
            )
    st.sidebar.success("インデックス作成完了")

# タイトル
st.title("Local LLM Chat")

# 会話の履歴を保管
if "messages" not in st.session_state:
    st.session_state.messages = []

# 会話の履歴をリセットするボタン
if st.sidebar.button("会話をリセット"):
    st.session_state.messages = []

# 会話の履歴を表示
for m in st.session_state.messages:
    with st.chat_message(m["role"]):
        st.write(m["content"])


prompt = st.chat_input("メッセージを入力")

client = OpenAI(
    api_key="ollama",   
    base_url="http://localhost:12000/v1"
)

if prompt:

    # ユーザーのプロンプトを表示
    with st.chat_message("user"):
        st.write(prompt)

    # RAG検索
    query_embed = ollama_embed(prompt)
    results = st.session_state.collection.query(
        query_embeddings=[query_embed],
        n_results=2
    )

    # {
    #     "ids":
    #     "documents": [["doc1", "doc2"]]
    #     "distances": [[XXX, XXX]]
    # }

    if results["documents"]:
        context_text = "\n".join(results["documents"][0])
        rag_prompt = f"""
        以下は関連ドキュメントの抜粋です。
        {context_text}
        この情報を参考に以下の質問に答えてください。
        {prompt}
        """
        final_user_prompt = rag_prompt
    else:
        final_user_prompt = prompt

    st.session_state.messages.append({"role": "user", "content": final_user_prompt})

    if system_prompt.strip():
        messages = [{"role": "system", "content": system_prompt}] + st.session_state.messages
    else:
        messages = st.session_state.messages
    
    # LLMの返答を表示
    with st.chat_message("assistant"):
        placeholder = st.empty()
        stream_response = ""
        stream = client.chat.completions.create(
            model=model,
            messages=messages,
            temperature=temperature,
            stream=True
        )
        for chunk in stream:
            stream_response += chunk.choices[0].delta.content
            placeholder.write(stream_response)

    # 会話の履歴を保存
    
    st.session_state.messages.append({"role": "assistant", "content": stream_response})

のコードを
OpenAI のAPIではなく
Ollama のelyza:jp8bを使用するようにコードを変更

openai の依存を削除し、/api/chat(ストリーミング)を requests で直接呼び出し。
モデル既定値を elyza:jp8b に変更。
Ollama のベースURL をサイドバーで指定可能(既定: http://localhost:11434)。埋め込みも同じURLを使用。
温度などは options 経由で送信。
ストリーミングは1行ごとの JSON を読み、message.content を随時描画。

import streamlit as st
import chromadb
from docx import Document
import requests
import json

# -----------------------------
# 共通設定
# -----------------------------
st.set_page_config(page_title="Local LLM Chat")

st.sidebar.title("設定")
ollama_base_url = st.sidebar.text_input("Ollama ベースURL", value="http://localhost:11434")
model = st.sidebar.text_input("モデル名", value="elyza:jp8b")  # 既定を elyza:jp8b に
temperature = st.sidebar.slider("temperature", 0.0, 2.0, 0.3, 0.1)
system_prompt = st.sidebar.text_area(
    "System Prompt",
    "あなたは有能なアシスタントです。日本語で回答してください",
)

# -----------------------------
# ChromaDB の設定
# -----------------------------
DB_DIR = "./chroma_db"
chroma_client = chromadb.PersistentClient(path=DB_DIR)

if "collection" not in st.session_state:
    st.session_state.collection = chroma_client.get_or_create_collection(
        name="local_docs"
    )

# -----------------------------
# Ollama API ユーティリティ
# -----------------------------
def ollama_embed(text: str):
    """Ollama の /api/embeddings を使ってベクトルを取得"""
    url = f"{ollama_base_url}/api/embeddings"
    r = requests.post(
        url,
        json={"model": "nomic-embed-text", "prompt": text},
        timeout=60,
    )
    r.raise_for_status()
    data = r.json()
    return data["embedding"]

def ollama_chat_stream(messages, model_name: str, temperature: float = 0.3):
    """
    Ollama の /api/chat ストリームを逐次 yield するジェネレータ。
    messages: [{"role":"system|user|assistant", "content":"..."}]
    """
    url = f"{ollama_base_url}/api/chat"
    payload = {
        "model": model_name,
        "messages": messages,
        "stream": True,
        "options": {
            "temperature": float(temperature)
        }
    }
    with requests.post(url, json=payload, stream=True) as r:
        r.raise_for_status()
        for line in r.iter_lines(decode_unicode=True):
            if not line:
                continue
            # 各行は JSON。末尾で {"done":true} が来る
            try:
                obj = json.loads(line)
            except json.JSONDecodeError:
                continue
            if obj.get("done"):
                break
            msg = obj.get("message", {})
            delta = msg.get("content", "")
            if delta:
                yield delta

# -----------------------------
# Word 読み込み & チャンク
# -----------------------------
def load_word_document(file):
    return "\n".join(para.text for para in Document(file).paragraphs)

def split_text(text):
    chunk_size = 200
    overlap = 50
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunks.append(text[start:end])
        start += chunk_size - overlap
    return chunks

# -----------------------------
# UI: アップロード → インデックス
# -----------------------------
uploaded_files = st.sidebar.file_uploader(
    "Wordファイルをアップロード(.docx)",
    type=["docx"],
    accept_multiple_files=True
)

if st.sidebar.button("インデックス作成"):
    try:
        for file in uploaded_files or []:
            text = load_word_document(file)
            chunks = split_text(text)
            for i, chunk in enumerate(chunks):
                embed = ollama_embed(chunk)
                st.session_state.collection.add(
                    documents=[chunk],
                    embeddings=,
                    ids=[f"{file.name}_{i}"]
                )
        st.sidebar.success("インデックス作成完了")
    except Exception as e:
        st.sidebar.error(f"インデックス作成でエラー: {e}")

# -----------------------------
# 会話領域
# -----------------------------
st.title("Local LLM Chat")

if "messages" not in st.session_state:
    st.session_state.messages = []

# リセットボタン
if st.sidebar.button("会話をリセット"):
    st.session_state.messages = []

# 既存履歴の表示
for m in st.session_state.messages:
    with st.chat_message(m["role"]):
        st.write(m["content"])

# 入力
prompt = st.chat_input("メッセージを入力")

if prompt:
    # ユーザーの入力を表示
    with st.chat_message("user"):
        st.write(prompt)

    # --- RAG: 近傍検索 ---
    try:
        query_embed = ollama_embed(prompt)
        results = st.session_state.collection.query(
            query_embeddings=[query_embed],
            n_results=2
        )
    except Exception as e:
        results = {"documents": []}
        st.warning(f"RAG検索でエラーが発生しました: {e}")

    # ドキュメントの結合
    if results.get("documents"):
        context_text = "\n".join(results["documents"][0])
        rag_prompt = f"""
以下は関連ドキュメントの抜粋です。
{context_text}

この情報を参考に以下の質問に答えてください。
{prompt}
"""
        final_user_prompt = rag_prompt
    else:
        final_user_prompt = prompt

    # セッション履歴にユーザー発話(RAG統合後)を追加
    st.session_state.messages.append({"role": "user", "content": final_user_prompt})

    # Ollama へ投げる message 配列を組み立て
    chat_messages = []
    if system_prompt.strip():
        chat_messages.append({"role": "system", "content": system_prompt})
    # 既存履歴をそのまま渡す(role は "user"/"assistant")
    chat_messages.extend(st.session_state.messages)

    # --- ストリーミング応答 ---
    with st.chat_message("assistant"):
        placeholder = st.empty()
        stream_response = ""
        try:
            for delta in ollama_chat_stream(chat_messages, model, temperature):
                stream_response += delta
                placeholder.write(stream_response)
        except Exception as e:
            st.error(f"Ollama応答でエラーが発生しました: {e}")

    # 履歴へ保存
    st.session_state.messages.append({"role": "assistant", "content": stream_response})


国土数値情報の洪水浸水想定区域データをダウンロード

OGIS用データのダウンロード

国土数値情報の洪水浸水想定区域データをダウンロード

https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-A31.html

一番新しいものは
最新のデータは第4.0版(2022年度)に基づいています。データ詳細はこちら(データ作成年度:2022年度(令和4年度))

https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-A31a-v4_0.html
が最新のダウンロードになり

各都道府県で分かれている

静岡 世界測地系 2021年(令和3年) 141MB A31-21_22_GML.zip
を今回はダウンロード

ダウンロードしたら解凍

この中に
想定最大規模

SHP
を選択すると大量のシェープファイルがある

この中から適当に.shpファイルを
QGISにドラッグ&ドロップしてみる

今回は
A31-20-21_8505030001.shp
をドラッグ&ドロップ

なおレイヤは上から優先して表示しているので
Google map などはこの shpファイルの下になるように配置しないと
地図だけしか表示されないので注意

この地域図の中身を見ると文字化けする時があるので
geoJSONの方で確認する

この時に複数の座標が表示されるが
ファイル名のAから始まる番号は河川名のコードなので
探すとどの川なのかがわかる

また、この中には
https://nlftp.mlit.go.jp/ksj/gml/codelist/water_depth_code.html
にあるような浸水深ランクコードがあり
数が大きいほど危険度が増える

これを使って浸水深ランクにより表示を変えるようにする

これは
プロパティ > シンポロジで
第一定義から
連続値による定義に変更

値を
A31_205 を選ぶ

次に
ヒストグラム > 値の読み込み
これでどれくらいん割合で存在しているかを見れる

確認できたら
ヒストグラムから分類値へ変更

これで分類をクリックすると表示されるので
この状態で色を変更したり
表示する数値レベルの設定変更ができる

とりあえず
3−4を黄色
4−5を紫
5−6を赤にすると分かりやすい

そのほかにGIS取得できるサイト

https://www.e-stat.go.jp
総務省e-stat政府統計データ

分野
https://www.e-stat.go.jp/statistics-by-theme/
で気象などがあり
GIS以外にも情報がある

国土数値情報ダウンロードサイト
https://nlftp.mlit.go.jp/ksj/
データは
https://nlftp.mlit.go.jp/ksj/index.html
からダウンロード可能
https://nlftp.mlit.go.jp/ksj/index.html
で地価をダウンロード可能

https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-L01-2025.html
これがあれば立地状況や値段を調べることができる

国土数値情報ダウンロードサイト
https://nlftp.mlit.go.jp/cgi-bin/isj/dls/_choose_method.cgi

地理空間情報ライブラリー
https://service.gsi.go.jp/geolib/

QGISメモ

QGIS属性データの確認と編集

属性テーブルの操作をするには
読み込んだシェープファイル、今回なら
観光スポット(スポーツ)
を右クリックし
属性テーブルを開くを選択する

これでデータが出てくるので
ペンのアイコンの編集モードの切り替えをクリック

これでデータの変更が可能になる

次に
QGISデータのフィルタ機能

現在、シェープファイルの内容を全て同じ色にしているので
どれか識別をしにくい

やり方は
ファイル名を右クリックし
フィルタを選択

今回なら
観光スポット(スポーツ)
のシェープファイルを使う

この中で
SAFIELD002 が体育館を表しているので
これをダブルクリック

次に = をクリックし
すべて をクリックすると要素が出てくるので
この中から実験したいので
プールをダブルクリック

これでテストをクリックすると
プールの表示が消える

条件式を消したいのなら
クリアを押せばいい

今回は文字列だけど
データが数字なら
平均年収とかを条件式で識別して表示することもできる

そして
QGISの可視化手法

読み込むファイルを右クリックし
プロパティ > シンポロジ
で設定を変更できる

表示するマークを変えたいのなら
お気に入りを全シンボルに変更

自分でアイコンを取り込みたいのなら
シンプルマーカーをクリックし
シンボルレイヤ型を
シンプルマーカーから
ベクタフィールドマーカーに変える

さらにラスタ画像マーカーに変える

あとは画像を選択して大きさを設定すれば任意のマークに変えることができる

マークの変更の次はラベル

プロパティで
ラベルをクリック

デフォルトでは
なし
になっているので
単一定義に変更

次にVALUE のところを変更する
とりあえず体育館などのSAFIELD002にしておく

そして分かりやすくするために色を赤にする
文字の大きさも20ぐらいにして
OK
を押すと体育館などが赤い文字で表示される

次に
QGISのヒートマップ

ファイルをダブルクリック
プロパティで
シンポロジを
単一定義からヒートマップへ変更

色の選択をturbo に変える

これでヒートマップになるけど
地図が消えてしまう

これはプロパティで
シンポロジの
レイヤレンダリングをクリックし
不透明度を100%から
20%ぐらいにすれば地図が見えるようになる

RAGのためのPDF内容抽出

RAGのためのPDF内容抽出

以下のPDFファイルを読んで、ページごとに内容をテキストに変換してください。 
**重要なルール:** - 各ページのテキストの先頭に、必ず以下の形式でページ番号を記載してください。 例: `--- ページ 1 ---` - テキストは、元のドキュメントのレイアウト(段落、箇条書き、表など)をできるだけ維持し、読みやすい形式で出力してください。 - 特に表は、Markdownの表形式などで構造を明確にしてください。 - PDF内に画像が含まれている場合、その画像に書かれている文字や説明もテキストに含めてください。 - PDF全体の内容を省略せず、全て抽出してください。 **抽出されたテキストの例:** --- ページ 1 --- [ページ1のテキスト] --- ページ 2 --- [ページ2のテキスト] ... --- ページ N --- [ページNのテキスト]

としたけど

ご提供いただいたPDFファイルから、指定された形式(ページ番号の付与、レイアウトの維持、表のMarkdown変換など)でテキストを抽出するご依頼ですね。
残念ながら、私の内部ツールは、PDFファイルから画像や複雑なレイアウトを読み取り、ご指定の形式に変換する機能は備えておりません。UnstructuredやGemini APIといったOCRに特化した外部サービスを利用しない限り、高精度な抽出や構造化されたテキストの出力は困難です。
以前ご案内した通り、PDFから高精度なテキスト抽出を行い、RAGシステムで利用するためには、以下のような外部ツールをご自身でご利用いただくのが最も確実です。
推奨される方法:
1. GeminiのWeb UIを利用して、PDFをアップロードします。
2. 以前ご案内した以下のプロンプトをGeminiに送信して、PDFのテキスト抽出と整形を依頼します。

以下のPDFファイルを読んで、ページごとに内容をテキストに変換してください。 **重要なルール:** - 各ページのテキストの先頭に、必ず以下の形式でページ番号を記載してください。 例: `--- ページ 1 ---` - テキストは、元のドキュメントのレイアウト(段落、箇条書き、表など)をできるだけ維持し、読みやすい形式で出力してください。 - 特に表は、Markdownの表形式などで構造を明確にしてください。 - PDF内に画像が含まれている場合、その画像に書かれている文字や説明もテキストに含めてください。 - PDF全体の内容を省略せず、全て抽出してください。

となるので
これで実験する

Gemini 2.5 flash では処理する時に同じメッセージが出るだけ
Gemini 2.5 pro にすると画面に憑依される

なお gemini によれば
割して保存したほうがRAGの性能を最大限に引き出すことができる

結論から言うと、分割して保存したほうがRAGの性能を最大限に引き出すことができます。
以下にその理由と、具体的な保存方法について説明します。

分割して保存すべき理由

RAG(Retrieval-Augmented Generation)は、質問に関連する情報をデータベースから検索し、その情報をもとに回答を生成する仕組みです。このプロセスにおいて、元のドキュメントをどのように分割して保存するか(チャンク化)が非常に重要になります。
1. 検索精度の向上:
* 質問に最も関連する小さなチャンクを正確に特定できるようになります。
* もしドキュメント全体を一つの大きなテキストとして保存した場合、検索結果にノイズ(質問と関係ない情報)が多く含まれてしまい、関連性の低い情報に基づいて回答を生成してしまうリスクが高まります。
2. 生成される回答の質の向上:
* LLM(大規模言語モデル)は、与えられた情報(コンテキスト)の量が増えすぎると、重要な情報を見落としたり、まとまりのない回答を生成したりすることがあります(コンテキスト過多の問題)。
* 適切なサイズに分割することで、LLMに与える情報を質問に関連する部分だけに絞り込むことができ、より正確で簡潔な回答を引き出すことができます。
3. コストと処理速度の最適化:
* 大きなテキストを処理するには、より多くの計算リソースと時間がかかります。
* 分割することで、検索と生成のプロセスが効率化され、全体の処理速度が向上し、APIコストの削減にもつながります。

具体的な保存方法

PDFファイルから抽出したテキストをRAG用に保存する際は、以下のような方法が考えられます。

1. ページごとに分割して保存する

最もシンプルで効果的な方法です。抽出したテキストを、元のページごとに異なるテキストファイルとして保存します。
保存方法の例:
* page_01.txt
* page_02.txt
* page_03.txt
* …
この方法では、質問に関連する情報がどのページにあるか検索し、そのページ全体のテキストをLLMに渡すことができます。ただし、1ページに非常に多くの情報が含まれている場合、さらに小さな単位に分割することも検討が必要です。

2. セクションや段落ごとに分割して保存する

ページの区切りに関係なく、内容のまとまり(見出し、段落、箇条書きなど)ごとに分割して保存する方法です。
保存方法の例:
* section_契約概要.txt
* section_保障内容.txt
* section_保険料の支払い.txt
* section_よくある質問.txt
* …
この方法の利点は、より意味のある単位で情報を分割できることです。例えば、「保険料の支払いについて」という質問に対しては、「保険料の支払い」のセクションを直接検索して利用できます。

3. チャンクサイズを指定して分割する(高度な方法)

テキストを固定の文字数や単語数で区切り、オーバーラップ(重複)を持たせてチャンク化する方法です。
* 例えば、「文字数500文字、重複200文字」といったルールで分割します。
* この方法は、検索エンジンがより多くの関連情報を捉えるのに役立ちます。
この方法は、特に長大なドキュメントや、特定のテーマが複数のページにまたがっている場合に有効です。

まとめ

まずは「ページごとに分割して保存する」方法から始めるのがおすすめです。
そして、RAGの利用を始める際には、まず抽出したテキストをデータベース(ベクトルデータベースなど)に格納し、そのデータベースに対して質問を投げることになります。質問に最も関連性の高いテキストチャンクが検索され、LLMに渡されて回答が生成されるという流れです。

ということで
プロンプトを

PDFファイルを読み込み、その内容をページごとにテキストに変換し、指定された形式でファイルに保存してください。
重要なルール:
* 各ページのテキストの先頭に、必ず以下の形式でページ番号を記載してください。例: --- ページ 1 ---
* テキストは、元のドキュメントのレイアウト(段落、箇条書き、表など)をできるだけ維持し、読みやすい形式で出力してください。
* 特に表は、Markdownの表形式などで構造を明確にしてください。
* PDF内に画像が含まれている場合、その画像に書かれている文字や説明もテキストに含めてください。
* PDF全体の内容を省略せず、全て抽出してください。
* 抽出したテキストは、ページごとに分割して、それぞれ異なるファイルとして保存してください。
* ファイル名は「page_ページ番号.txt」としてください(例: page_1.txt)。
* 出力は、保存されたファイルとその場所を示すメッセージのみにしてください。

というように変更しファイルに保存するようにする

しかし処理する約款ページは270Pあるため、分割して実験することにする

次に
Echo 証券の内容の抽出

最初にJSONで抽出を試したが、いくつかの要素が抜けている
これをテキストファイルで抽出すると
ほぼ全て抽出できる
ただし、⭕️❌での記載部分は反映されない

このため一度テキストで抽出し修正することにする

QGISのためのCSVデータのダウンロード

QGISのためのCSVデータのダウンロード

地図は表示できたので
CSVファイルなどをダウンロードし
内容を重ねて表示する

今回は世田谷区GISオープンデータを使う

https://data-setagaya.opendata.arcgis.com
この中から
観光スポット > スポーツ
https://data-setagaya.opendata.arcgis.com/search?tags=%25E8%25A6%25B3%25E5%2585%2589%25E3%2582%25B9%25E3%2583%259D%25E3%2583%2583%25E3%2583%2588

ダウンロード形式が選べるので
とりあえず
CSV
GeoJSON
シェープファイル
KML

シェープファイルは圧縮されているので解凍すると
ccpg
dbf
prj
shp
shx
xml
のファイルが入っている

mkdir qgis
cd qgis 
mv ~/Downloads/%E* .

でダウンロードしたファイルを移動

unzip %E8%A6%B3%E5%85%89%E3%82%B9%E3%83%9D%E3%83%83%E3%83%88%EF%BC%88%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%84%EF%BC%89.zip

で解凍

これをデータソースアイコンから読み込んで使う

今回はCSVを使う予定だけど
そのまま使おうとしたらCRSを指定しないとだめだった

2つ選べるようだけどわからないので調べた

* プロジェクトCRS:EPSG:3857 – WGS 84 / Pseudo-Mercator
* これは、GoogleマップやOpenStreetMapなど、多くのWeb地図サービスで採用されているCRSです。
* 「プロジェクトCRS」は、現在開いているQGISプロジェクト全体に適用されるCRSです。
* QGISでは、異なるCRSを持つ複数のレイヤを重ねて表示することができますが、その際には、すべてのレイヤがプロジェクトCRSに合わせて自動的に再投影されます。これにより、各レイヤが正しい位置に表示されます。
* つまり、プロジェクトCRSは、QGISの地図画面(マップキャンバス)の「地図の枠組み」を定義するCRSと言えます。
* デフォルトCRS:EPSG:4326 – WGS 84
* これは、GPSなどで一般的に使用される緯度・経度で位置を表す地理座標系です。
* 「デフォルトCRS」は、新しいレイヤを作成する際に、QGISが初期値として推奨するCRSです。
* CSVファイルのように、メタデータ(座標系の情報)が含まれていないデータを読み込む場合、QGISはどのCRSを使用すべきか判断できません。その際に、初期値として提示されるのがこのデフォルトCRSです。
* CSVファイルに緯度経度(WGS 84)のデータが入っている場合、このデフォルトCRSである EPSG:4326 – WGS 84 を選択するのが最も一般的で正しい選択となります。

* 「プロジェクトCRS」を選択する
* これは、読み込むCSVデータの座標が、すでにプロジェクトCRSである EPSG:3857 の形式で記録されている場合に選択します。
* 例えば、メートル単位の座標値(x, y)がCSVに入っている場合などです。
* 「デフォルトCRS」を選択する
* これは、読み込むCSVデータの座標が、緯度・経度(EPSG:4326)で記録されている場合に選択します。
* Webからダウンロードした多くのデータは、この緯度経度形式で提供されるため、通常はこちらを選択することが多いです。

OpenStreetMapやGoogleマップのレイヤーを背景地図として利用する場合、「プロジェクトCRS」を選択するのが正しい選択となります。
その理由は以下の通りです。
1. OpenStreetMapやGoogleマップのCRSは「EPSG:3857」
* これらのWeb地図サービスは、すべて EPSG:3857 – WGS 84 / Pseudo-Mercator というCRSで提供されています。これは、Web上で地図をスムーズに表示するために最適化された座標系です。
* QGISでは、これらのレイヤーを読み込むと、プロジェクトCRSも自動的に EPSG:3857 に設定されることが一般的です。
2. QGISの「オンザフライ(On-the-fly)再投影」機能
* QGISには、異なるCRSを持つレイヤーを重ねて表示できる「オンザフライ再投影」という機能があります。
* これは、すべてのレイヤーをプロジェクトCRSに合わせて、一時的に再投影して表示する機能です。
* つまり、あなたが追加しようとしているCSVデータが、もし緯度・経度 (EPSG:4326) であっても、QGISはそれを自動的に EPSG:3857 に変換して、OpenStreetMapなどの地図上にぴったり重ねて表示してくれます。
結論として
* 今回のケース(OpenStreetMapやGoogleマップが背景にある)では、CSVデータの座標系がEPSG:3857であると仮定して「プロジェクトCRS」を選択するのが合理的です。
* もしCSVデータが緯度・経度 (EPSG:4326) であり、かつ、プロジェクトのCRSがEPSG:3857である場合でも、QGISが自動的に再投影してくれますので、基本的には問題ありません。
ただし、データそのもののCRSが何かを正しく理解しておくことは非常に重要です。 もしCSVデータが明らかに緯度・経度で書かれているのに「プロジェクトCRS」として読み込んでしまうと、QGISはCSVデータの座標値をEPSG:3857の座標値として解釈してしまい、本来とは全く異なる場所に表示される可能性があります。
正しく行うための手順
1. CSVデータの中身を確認する
* CSVファイルを開き、座標の列が緯度・経度(例:35.xxxx, 139.xxxx)か、メートル単位の座標(例:13900000, 4100000)かを確認します。
2. 適切なCRSを選択する
* CSVデータが緯度・経度の場合:**「デフォルトCRS(EPSG:4326)」**を選択します。
* CSVデータがメートル単位の座標の場合:**「プロジェクトCRS(EPSG:3857)」**を選択します。

これが gemini の回答

今回はプロジェクトCRSを使うことにする

これで追加しても何故か海の上に表示される

なので
CSVファイルを右クリックし
レイヤのCRS > レイヤのCRS設定
6677で検索し
選んでOKをすれば適用される

このままだと点だけで分かりにくいので
CSVファイルを右クリックし
ラベルを表示をクリック

これで施設名が表示される

また
CSVファイルを右クリックし
プロパティ > ラベル

フォントや文字の色などの変更が可能

これでマップで施設名を色を変更して表示できる
他の地域でもできるはず

ファイルの中のCISに座標が書いてある

この座標はWGS84になっている
このため google map でも使える緯度経度になっている

GeoJSONを読み込むには
データソースで
ベクタをクリックし
.geojsonのファイルをひらけばOK

このままで分かりにくいのなら
geoJSONファイルをダブルクリックして
プロパティ > シンポロジ
で設定のフォントや色を変えればいい

次に
QGISへシェープファイルの読み込み

シェープファイルも
データソース > ベクタで開くが
Shpファイルを開く

これで追加すればOK

これも同様に
ファイル名をダブルクリックして
プロパティ > シンポロジ
でフォント、色を変更できる

分かりやすくするために新規にレイヤーを作成する

レイヤ > レイヤを作成 > 新規一次スクラッチレイヤ

ジオメトリをポリゴン

新規属性の名前が必要なので
ポリゴンテスト
とする

これで属性リストに追加をクリック

これでOKを押すと追加されるが
このままだと何も追加されていない

なので追加された
新規スクラッチレイヤを選択し
ポリゴン地図の追加をクリック

この横にできた矢印みたいなアイコンをクリックすると
ポリゴンを作成することができる
この状態でクリックしていくと座標が設置され
複雑なポリゴンが表示できる
形が決まったら手のアイコンをクリックするとポリゴンの作成が決定になる

なおこのポリゴンはescキーを押すと削除可能

QGISの使い方メモ

QGISの使い方メモ

まずは地図表示でOpenStreetMapを表示

次にGoogle Map を追加したいので
XYZタイルを右クリックし
新規接続を選択

URLを追加することで地図を追加できる
このURLは
QGIS Google Map
で検索すると出てくる

https://qiita.com/nigo1973/items/cb023983a498bcf8e73f
を参考に

名前を
Google Map衛星画像
にして

保存すれば追加できる

これで衛星画像が表示される

通常のgoogle map なら

https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}

のURLを同様に
XYZスタイルを右クリック
新規追加で追加して
名前を Google Map にして
URL を

https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}

にすれば追加できる

https://stickboost.net/qgis_googlemap
を参考にした

その他の汎用地図リンクは

Google Maps: https://mt1.google.com/vt/lyrs=r&x={x}&y={y}&z={z}
Google Satellite: http://www.google.cn/maps/vt?lyrs=s@189&gl=cn&x={x}&y={y}&z={z}
Google Satellite Hybrid: https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}
Google Terrain: https://mt1.google.com/vt/lyrs=t&x={x}&y={y}&z={z}
Google Roads: https://mt1.google.com/vt/lyrs=h&x={x}&y={y}&z={z}
国土地理院地図: https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png
OSM(Open Street Map): http://tile.openstreetmap.org/{z}/{x}/{y}.png

地図を複数表示している場合
上に設定しているレイヤーが表示される
つまり
衛星画像を1番上にすると
地図じゃなくて衛星画像が表示される

次に演習用CSVデータのダウンロード

地図は表示できたので
CSVファイルなどをダウンロードし
内容を重ねて表示する

今回は世田谷区GISオープンデータを使う

https://data-setagaya.opendata.arcgis.com
この中から
観光スポット > スポーツ
https://data-setagaya.opendata.arcgis.com/search?tags=%25E8%25A6%25B3%25E5%2585%2589%25E3%2582%25B9%25E3%2583%259D%25E3%2583%2583%25E3%2583%2588

ダウンロード形式が選べるので
とりあえず
CSV
GeoJSON
シェープファイル
KML

シェープファイルは圧縮されているので解凍すると
ccpg
dbf
prj
shp
shx
xml
のファイルが入っている

mkdir qgis
cd qgis 
mv ~/Downloads/%E* .

でダウンロードしたファイルを移動

unzip %E8%A6%B3%E5%85%89%E3%82%B9%E3%83%9D%E3%83%83%E3%83%88%EF%BC%88%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%84%EF%BC%89.zip

で解凍

これをデータソースアイコンから読み込んで使う

今回はCSVを使う予定だけど
そのまま使おうとしたらCRSを指定しないとだめだった

2つ選べるようだけどわからないので調べた

* プロジェクトCRS:EPSG:3857 – WGS 84 / Pseudo-Mercator
* これは、GoogleマップやOpenStreetMapなど、多くのWeb地図サービスで採用されているCRSです。
* 「プロジェクトCRS」は、現在開いているQGISプロジェクト全体に適用されるCRSです。
* QGISでは、異なるCRSを持つ複数のレイヤを重ねて表示することができますが、その際には、すべてのレイヤがプロジェクトCRSに合わせて自動的に再投影されます。これにより、各レイヤが正しい位置に表示されます。
* つまり、プロジェクトCRSは、QGISの地図画面(マップキャンバス)の「地図の枠組み」を定義するCRSと言えます。
* デフォルトCRS:EPSG:4326 – WGS 84
* これは、GPSなどで一般的に使用される緯度・経度で位置を表す地理座標系です。
* 「デフォルトCRS」は、新しいレイヤを作成する際に、QGISが初期値として推奨するCRSです。
* CSVファイルのように、メタデータ(座標系の情報)が含まれていないデータを読み込む場合、QGISはどのCRSを使用すべきか判断できません。その際に、初期値として提示されるのがこのデフォルトCRSです。
* CSVファイルに緯度経度(WGS 84)のデータが入っている場合、このデフォルトCRSである EPSG:4326 – WGS 84 を選択するのが最も一般的で正しい選択となります。

* 「プロジェクトCRS」を選択する
* これは、読み込むCSVデータの座標が、すでにプロジェクトCRSである EPSG:3857 の形式で記録されている場合に選択します。
* 例えば、メートル単位の座標値(x, y)がCSVに入っている場合などです。
* 「デフォルトCRS」を選択する
* これは、読み込むCSVデータの座標が、緯度・経度(EPSG:4326)で記録されている場合に選択します。
* Webからダウンロードした多くのデータは、この緯度経度形式で提供されるため、通常はこちらを選択することが多いです。

OpenStreetMapやGoogleマップのレイヤーを背景地図として利用する場合、「プロジェクトCRS」を選択するのが正しい選択となります。
その理由は以下の通りです。

1. OpenStreetMapやGoogleマップのCRSは「EPSG:3857」
* これらのWeb地図サービスは、すべて EPSG:3857 – WGS 84 / Pseudo-Mercator というCRSで提供されています。これは、Web上で地図をスムーズに表示するために最適化された座標系です。
* QGISでは、これらのレイヤーを読み込むと、プロジェクトCRSも自動的に EPSG:3857 に設定されることが一般的です。

2. QGISの「オンザフライ(On-the-fly)再投影」機能
* QGISには、異なるCRSを持つレイヤーを重ねて表示できる「オンザフライ再投影」という機能があります。
* これは、すべてのレイヤーをプロジェクトCRSに合わせて、一時的に再投影して表示する機能です。
* つまり、あなたが追加しようとしているCSVデータが、もし緯度・経度 (EPSG:4326) であっても、QGISはそれを自動的に EPSG:3857 に変換して、OpenStreetMapなどの地図上にぴったり重ねて表示してくれます。

結論として
* 今回のケース(OpenStreetMapやGoogleマップが背景にある)では、CSVデータの座標系がEPSG:3857であると仮定して「プロジェクトCRS」を選択するのが合理的です。

* もしCSVデータが緯度・経度 (EPSG:4326) であり、かつ、プロジェクトのCRSがEPSG:3857である場合でも、QGISが自動的に再投影してくれますので、基本的には問題ありません。

ただし、データそのもののCRSが何かを正しく理解しておくことは非常に重要です。
もしCSVデータが明らかに緯度・経度で書かれているのに「プロジェクトCRS」として読み込んでしまうと、
QGISはCSVデータの座標値をEPSG:3857の座標値として解釈してしまい、本来とは全く異なる場所に表示される可能性があります。

正しく行うための手順
1. CSVデータの中身を確認する
* CSVファイルを開き、座標の列が緯度・経度(例:35.xxxx, 139.xxxx)か、メートル単位の座標(例:13900000, 4100000)かを確認します。

2. 適切なCRSを選択する
* CSVデータが緯度・経度の場合:**「デフォルトCRS(EPSG:4326)」**を選択します。
* CSVデータがメートル単位の座標の場合:**「プロジェクトCRS(EPSG:3857)」**を選択します。

これが gemini の回答

今回はプロジェクトCRSを使うことにする

これで追加しても何故か海の上に表示される

なので
CSVファイルを右クリックし
レイヤのCRS > レイヤのCRS設定
6677で検索し
選んでOKをすれば適用される

このままだと点だけで分かりにくいので
CSVファイルを右クリックし
ラベルを表示をクリック

これで施設名が表示される

また
CSVファイルを右クリックし
プロパティ > ラベル

フォントや文字の色などの変更が可能

これでマップで施設名を色を変更して表示できる
他の地域でもできるはず

PDFからGPTの連携

PDFからGPTの連携

chatGPT とPDFの連携
https://www.udemy.com/course/chatgpt-linebot-gcp/learn/lecture/39774396?start=18#overview
を参考にする

ただしテキスト情報のみのPDFでの話

PDFと連携するには
まずPDFからテキスト情報を抽出する

PDFにはテキスト以外にも画像データがあるが
今回の講座は文字のみを想定

PDFから文字を抽出し
それが3000文字の場合
1000文字単位に分割すると3つの文章に分けることができる

ちなみにこっちは概要

https://www.udemy.com/course/chatgpt-linebot-gcp/learn/lecture/39774404#overview

実践でllama index を使っている

https://www.udemy.com/course/chatgpt-linebot-gcp/learn/lecture/39877052#overview
でPDFの差し替えが載っている

ただい、これだと画像の入っているPDFはできないので他を探す

QGISのインストール

QGISのインストール

最新版はLTRのついてるものになり
意味は
Long Term Release

Ubuntu のLTSみたいな感じ

今回は Macで行う

https://qgis.org
が公式サイト

Downloadをクリックすると寄付するか聞かれるけど
最低でも10ユーロからになるので
今回は
Skip it and go to download
でダウンロード画面に遷移する

ダウンロード時には2つ選択があり
Long term version for Mac
なら安定版

Regular Version for Mac
なら最新版になる

今回は安定版を使うので
Long term version を選択

1.7GBあるので時間がかかる

なおインストーラーでこの容量で実際には2.7GB消費する

なお、そのままだと起動できないので
Mac で
システム設定 > プライバシーとセキュリティ
で下へスクロールすると
お使いのMacを保護するためにQGIS-LTRがブロックされました
とあるので
このまま開くをクリック

これでようやく起動する

これは
qgis mac 開けない
で検索し
MacでQGISが開けない場合の対応方法
を参考にした

graphDB graphRAG の他の用途

graphDB graphRAG の他の用途として
経済・金融データ分析
例: 経済指標・株価・商品価格・為替などの相関関係をグラフ構造に保存し、RAGで「現状と過去の類似パターン」を取得。
メリット:
価格・イベント・ニュースの因果関係を把握できる
RAGで時系列説明やリスク分析を生成可能
シナリオ:
質問:「過去10年間でWTIが急落したときの共通要因は?」
処理: GraphDBで急落イベントと関連経済指標を探索 → RAGが文章化

これを作成したい

GPTの答えを自分なりにまとめる

急落イベントの自動生成
閾値は2通り用意(どちらか一致でイベント化):
* 絶対閾値: return1d <= -0.05 * 統計的閾値: zscore <= -2.0 // 急落イベント生成 MATCH (a:Asset {symbol:"WTI"})-[:HAS_PRICE]->(p:Price)
WHERE p.return1d <= -0.05 OR p.zscore <= -2.0 MERGE (e:Event:PriceDrop {date:p.date, asset:"WTI"}) ON CREATE SET e.threshold = CASE WHEN p.return1d <= -0.05 THEN "ret<=-5%" ELSE "z<=-2" END MERGE (e)-[:ON_ASSET]->(a)
MERGE (e)-[:NEARBY_PRICE {lag:0}]->(p);

共起(要因候補)のひも付け
イベント±3営業日程度の窓で、関連しそうなファクトを CO_OCCURS_WITH で結ぶ。

// 経済指標サプライズと共起
MATCH (e:Event:PriceDrop {asset:"WTI"})
MATCH (ip:IndicatorPrint)
WHERE date(ip.date) >= date(e.date)-duration('P3D') AND date(ip.date) <= date(e.date)+duration('P3D')
MERGE (e)-[:CO_OCCURS_WITH {window:"±3d"}]->(ip);

// ドル指数(DXY)急騰など他資産の大変動
MATCH (e:Event:PriceDrop {asset:"WTI"})
MATCH (p:Price {asset:"DXY"})
WHERE date(p.date)=date(e.date) AND (p.return1d >= 0.01 OR p.zscore >= 2.0)
MERGE (e)-[:CO_OCCURS_WITH {window:"0d"}]->(p);

// ニュース(トピック抽出済み)も結ぶ
MATCH (e:Event:PriceDrop {asset:"WTI"}), (n:News)
WHERE date(n.date) >= date(e.date)-duration('P1D') AND date(n.date) <= date(e.date)+duration('P1D')
AND n.topic IN ["OPEC","在庫","中東地政学","需要見通し","景気後退懸念"]
MERGE (e)-[:CO_OCCURS_WITH {window:"±1d"}]->(n);

自動タグ付け(要因ノード化)
共起オブジェクトから 要因タグを抽出・集計して :Factor を作り、イベントと結ぶ。

// 例:DXY急騰なら「ドル高」を付与
MATCH (e:Event:PriceDrop)-[:CO_OCCURS_WITH]->(p:Price {asset:"DXY"})
WHERE p.zscore >= 2.0 OR p.return1d >= 0.01
MERGE (f:Factor {name:"ドル高急伸"})
MERGE (e)-[:HAS_FACTOR {type:"market"}]->(f);

// 例:在庫サプライズ(EIA原油在庫が予想比+)
MATCH (e:Event:PriceDrop)-[:CO_OCCURS_WITH]->(ip:IndicatorPrint)<-[:PUBLISHED_AS]-(:Indicator {code:"EIA_CRUDE_INV"})
WHERE ip.surprise > 0
MERGE (f:Factor {name:"EIA在庫予想超過(増)"})
MERGE (e)-[:HAS_FACTOR {type:"supply"}]->(f);

// 例:ニューストピックから
MATCH (e:Event:PriceDrop)-[:CO_OCCURS_WITH]->(n:News)
WHERE n.topic IN ["OPEC","増産","協調減産不履行"]
MERGE (f:Factor {name:"OPEC供給ニュース"})
MERGE (e)-[:HAS_FACTOR {type:"policy"}]->(f);

回答から見ると
急落イベントの定義が必要

そして要員候補を定義し
Cyperで紐付け定義している
この要員候補が共起となるので

この共起をもとに
要因タグを抽出・集計して :Factor を作り、イベントと結ぶ

過去10年の共通要因」を求めるCypherを作成

ここまでできたら
GraphRAG(LlamaIndex例)
を作成

ゼロからやっていくとして やることの順番

0. 目的と範囲の固定(30分で決め切る)
目的:WTI急落の共通要因を、数値+ニュース根拠つきで説明・検知できる。
やること
* 期間:直近10年(例:2015-01-01〜)
* 対象:WTI, DXY, EIA原油在庫, 主要マクロ(CPI/NFP/ISM), ニュース見出し
* 急落定義:ret1d ≤ -5% または zscore ≤ -2
* 共起窓:±3日
完了条件:上記をREADMEに明文化。

1. 環境・リポジトリ準備
やること
* Gitリポジトリ作成:/graph-wti-poc
* Python 3.11系、仮想環境
* 依存:neo4j, pandas, numpy, python-dateutil, llama-index-graph-stores-neo4j, llama-index
* Neo4j(Docker可)起動、パスワード設定
完了条件:make setup && make testでimportなしのスモークテストが通る。

2. データの最小セットを落とす(まずはCSVでOK)
やること
* data/price_wti.csv(日次 Date, Close)
* data/price_dxy.csv
* data/eia_crude_inv.csv(週次 Date, actual, consensus)
* data/macro_{cpi,nfp,ism}.csv(Date, actual, consensus)
* data/news_titles.csv(Date, title, source, topic?)
完了条件:CSVが所定フォーマットで保存され、scripts/validate_csv.pyで列チェックOK。

3. グラフ・スキーマ定義
やること
* ノード:Asset, Price, Indicator, IndicatorPrint, News, Event:PriceDrop, Factor
* リレーション:HAS_PRICE, PUBLISHED_AS, ON_ASSET, NEARBY_PRICE, CO_OCCURS_WITH, HAS_FACTOR, MENTIONS
* インデックス/制約:Asset(symbol), Price(date,asset), Indicator(code), IndicatorPrint(date,code)
完了条件:scripts/init_schema.cypherを一発実行で作成される。

4. ETL(投入)——最小データ→Neo4j
やること
* scripts/load_assets.py(WTI/DXYなど)
* scripts/load_prices.py(ret1d/zscore計算込み)
* scripts/load_indicators.py(surprise = actual−consensus)
* scripts/load_news.py(topicは暫定でも可)
完了条件:Neo4j上でノード・関係の件数が期待範囲にある(簡易Cypherで確認)。

5. 急落イベント生成
やること
* jobs/mk_price_drop.cypher
* ルール① ret1d ≤ -0.05
* ルール② zscore ≤ -2.0
* イベント→資産・当日価格ノードへリンク
完了条件:MATCH (:Event:PriceDrop)で複数件が生成される。

6. 共起リンク付け(±3日)
やること
* jobs/link_cooccurrence.cypher
* Event ↔ IndicatorPrint(±3日)
* Event ↔ Price(DXY)(同日/±1日、急騰条件付き)
* Event ↔ News(±1日、topicフィルタ)
完了条件:各EventにCO_OCCURS_WITHが最低1件以上付く。

7. 要因タグ自動付与(Factor化)
やること
* ルールベースで開始(後でMLに発展)
* DXY急騰→「ドル高急伸」
* EIA在庫サプライズ>0→「在庫増サプライズ」
* News.topic in {OPEC, 需要減速, 地政学}→対応Factor
* jobs/mk_factors.cypher
完了条件:MATCH (e:Event)-[:HAS_FACTOR]->(f)がヒット、因子名の重複がない。

8. 集計クエリ(分析の芯)
やること
* TOP要因:頻度ランキング
* 事例リスト:各イベントと要因・信号の要約
* 反証:急落“でない”日との比較(同じ要因の出現率差)
完了条件:Cypher 3本がqueries/に保存され、結果がCSV/JSONで出力できる。

9. GraphRAG 接続(LlamaIndex最小構成)
やること
* Neo4j GraphStore接続
* ニュース本文(任意)をVector Index化
* 「イベント周辺サブグラフ抽出→要約」QueryEngine
完了条件:python rag_answer.py “過去10年のWTI急落の共通要因は?”が自然文で答えを返す。

10. 評価(人間の目で妥当性チェック)
やること
* ランダムに5〜10イベントを抽出し、当日の実ニュースと比較
* 誤検知/過検知の原因をメモ(topic付与・閾値調整など)
完了条件:改善ポイントのTODOが/docs/findings.mdにまとまる。

11. ルールのチューニング(再実行可能に)
やること
* 閾値・窓幅(±2/±3/±5日)、surpriseの標準化(z化)を切替可能に
* パラメタは.envやconfig.yamlへ
完了条件:make rebuild-with CONFIG=config.yamlで全処理が再実行できる。

12. アラート原型(将来の運用)
やること
* シンプルな条件2〜3本(例:WTI -3%以上&DXY +0.8%以上)
* 該当日の証跡(どの要因に引っかかったか)をテキスト生成
完了条件:CLIで日次スキャン→標準出力に最新ヒットを表示。

13. 可視化ミニUI(任意:後付け)
やること
* Streamlit or Dash
* タイムライン(急落イベント)
* ヒートマップ(要因×年)
* クリックで当日の共起ニュース・指標を展開
完了条件:ローカルで1ページ表示、スクリーンショット保存。

14. データ運用・再現性
やること
* Makefile:make all, make clean
* 週次/日次ジョブ(将来)向けに/jobs/を分離
* バックアップ手順(Neo4j dump, data/ バージョン付け)
完了条件:READMEに再現手順とバックアップ復元手順が記載。

まず動かす最短ルート(PoC 版)
1. 0→1→2(最小CSV)
2. 3(スキーマ)
3. 4(ETL投入)
4. 5(急落生成)
5. 6(共起リンク)
6. 8(要因TOP集計)
7. 9(RAG回答の骨格)
ここまでで**「数値+ニューストピックに基づく共通要因の説明」**が出ます。
その後、7(Factor強化)→11(チューニング)→12(アラート)→13(UI)で育てればOK。

GISについてのメモ

GISについてのメモ

GISは地理情報システムのこと

行政区域の境界
土地利用状況
地形図
統計情報
衛星画像
などがある

点、線、面で表せるような座標データなどはベクターデータで取り扱う

標高、気温など区切りができないものはラスターデータ

ベクターデータ
点、線、ポリゴンで地形を表現
位置情報と同時に属性を保持する
高い精度で地形の形状を表現
属性情報を持つことができる
道路、行政区域、建物の表現に適する
シェープファイル、GeoJSONなどの形式がある


地震があった時の震源の緯度経度が空間情報になる
これに加えて
属性のマグニチュード、発生時の時刻
などを保持できる
こういうのができるのがベクターデータの特徴

ラスターデータ
格子状のピクセルで地表を表現
位置情報と同時に画像を保持する
一定の解像度をもつグリッドセル
連続的な現象の表現に適する
衛星画像、標高データ、土地被覆などに利用
GeoTIFF JPEG 形式がある


温度とか標高

QGISでは両方のデータタイプを扱える

シェープファイル GeoJSON ファイルについて

シェープファイルは

ジオメトリデータ
点、線、面の空間情報

基本ファイル構成は
shp
shx
dpf

技術的制限は
サイズ上限
フィールド名制限

シェープファイルは最も広く使われるGISデータ形式
空間情報を収納するshp
インデックスを管理するshx
属性情報を保持するdbf
の3つのファイルが基本セットになる

なので渡される時には大抵zipなどで圧縮されてまとまっているが
展開した時に全て必要になるので注意

次にGeoJSON

JSON形式の拡張で
一般的なJSON形式を地理情報に拡張したファイル形式
Web開発との親和性が高い

様々なジオメトリ対応
Point
LineString
Polygon
など多様な空間オブジェクトを表現できる
複雑な地理データの扱いが可能

Webmapping での活用
特にwebベースのGISアプリで使われる
JS連携が容易

次にジオメトリオブジェクト
基本タイプ
Pint点
LineString線
これはPoint が2つ以上あれば線になる
Polygon面
これは3点あると面ができる
つまりいくつかの線を繋いで面にしている
なお面にするため、最初と最後の値は同じにする必要がある
これがないと面にならないので

これらをいくつか合わせたものが
複合タイプ
MultiPoint
MultiLineString
MultiPolygon

そしてまとめたものが
コレクション
複数のジオメトリをまとめる
GeometryCollection

GIS の応用分野

都市計画・地域計画
土地利用、都市のゾーニング、つまり用途地域の可視化、分析に使う
道路網、公共施設の配置の最適化
人工分布、将来的な発展エリアのシミュレーション

災害リスク分析、防災
洪水、土砂災害、地震などのハザードマップ
避難所の位置
避難経路の可視化
リスクエリアに住む人口、建物数の算出に使う

環境保全、自然資源管理
生態系、自然保護区のモニタリング
森林伐採、農地の変遷、水質管理などの分析
環境影響評価EIAへの活用

交通、インフラ計画
道路、鉄道、バスルートなどの交通ネットワーク分析
渋滞、通行量の可視化
交通事故のホットスポット解析

不動産、マーケティング
土地価格、地価公示データの可視化
商圏分析、店舗、支店などの立地選定
顧客分布の可視化とターゲティング

農業、林業
作物の栽培的値分析
土壌データ、気候条件のマッチング
農地の境界管理、収穫予測

教育、研究
地理学、地質学、歴史学などの研究支援
空間統計、空間相違の分析

GISデータの分析ソフト

ArcGIS
有償の業界標準ソフトウェア
機能が豊富で安定性が高い

QGIS
無料のOSS

pythonライブラリ
Geopandas
Shapley
など

今回使うのはQGIS
Mac
Windows
Linux
で動作する

多様なデータ形式に対応し
シェープファイル、GeoJSON、ラスターデータなど
様々なデータ形式が扱える

分析ツールとして
空間分析
地図作成
データ管理などが可能

プラグインによる拡張が可能

CRSとEPSGコード

CRSは
Coordinate reference system
で座標参照系

同じ座標と言っても原点をどこに取るかにより表し方が変わる
この決め方が座標系

EPSGは
European Petroleum Survey Group
で各国の測地系、投影法の識別子のこと

世界でよく使われる EPSGコードは
EPSG: 3857
Webメルカトル
OpenStreetMap など map系で使われる

EPSG: 4826
GPSで用いられる経緯度
世界測地系、WGS84で使われる

これらは世界標準で使われる

逆に地域特化のものものあり

日本でよく使われるEPSGコードは
EPSG:6677
平面直角座標系9系
日本測地系、東京都23区あたりに使われる

EPSG:6668〜6699
ユニバーサル横メルカトル座標系UTMで使われる
これは日本全体で使う

地図にプロットした時に座標系が異なるために
正しくプロットされないのはあるあるなので注意

複数の座標を可視化するならEPSGコードを揃える必要がある

この辺りはesri Japanの日本地図系で解説がある
https://www.esrij.com/gis-guide/coordinate-and-spatial/coordinate-system-japan/

あと
https://epsg.io
でコードを入力すれば詳細を見ることができる
例えば
6688
と入力すると
https://epsg.io/6688
で詳細の確認ができる

地域メッシュについて

GISでは日本全体を四角形に切って分析することがあり
その切り方を覚えておく

これが地域メッシュ
詳しくは
https://www.stat.go.jp/data/mesh/m_tuite.html
を参考に
大きさは約80km

第一次地域区画は5桁

第二次地域区画はこれを8分割したもので
約10km

第三次地域区画はさらに10等分したもので8桁になる
約一辺1kmでよく使われる

データを豊富にするなら
さらにこれを2分割した1/2地域メッシュで
これは500m

さらに細かいもので、これを2等分した
1/4地域メッシュもある
こちらは250m