Python 基本メモ データクラス

< コード例 >

クラス


'''
住所録データクラス
'''
from dataclasses import dataclass, field

@dataclass
class Address():
  gp: str        # グループ名
  list: str      # リスト名
  name: str      # 施設店舗名
  addr: str      # 住所
  tel: str       # 電話番号
  lat: float     # 緯度
  lon: float     # 経度
  zip: str       # 郵便番号
  icon: str      # マーカーアイコンファイル名

  ## 辞書型を使った場合 ##
  # dict = {"gp": GP, "list": LIST_NAME, "name": name, "addr": addr, "tel": tel,
  #                  "lat": llz["lat"], "lon": llz["lon"], "zip": llz["zip"], "icon": ICON_NAME}

import


import dclass.AddressData as ad

値の格納


# データクラス #
add: ad.Address = ad.Address(gp=GP, list=LIST_NAME, name=nm, addr=addr, tel="",
            lat=llz["lat"], lon=llz["lon"], zip=llz["zip"], icon=ICON_NAME)
print(add)
dictlist_dc.append(add)

Python 基本メモ ループでzipを使う

最近になって、Pythonを始めてみました。
Javaで行っていたWEBページのスクレープをPythonに移行中です。
今更ながらと言われるかも知れませんが、Javaに比べてコードが大きく減らせて、大幅生産性向上が出来て驚いてます。
そんな中で、リスト内包表記と同じく便利なので使ってるzip関数による複数変数ループを備忘録しておきます。

< コード例 >


## ++ 店名 住所 電話番号 走査 ++ ##  
for nm, addr, tel in zip(namelist, addrlist, tellist):
  
  # 経緯度、郵便番号ジオコーダー #
  if addr is not None and addr != "":
    geo = g.Google(addr)
    llz = geo.getLatLonZip()
    
    dict = {"gp": GP,"list": LIST_NAME, "name": nm, "addr": addr, "tel": tel, 
    "lat": llz["lat"], "lon": llz["lon"], "zip": llz["zip"], "icon": ICON_NAME}
    print(dict)
    dictlist.append(dict)
  
return dictlist

Leaflet Javascript ツールチップがポリラインに重ならないようにする

< コード例 >


  /**
   * チャートWaypointツールチップ位置、オフセット取得
   * 三角形にして頂点(カレント)への方向の領域を返す
   * 
   * @param {type} prev 前回データ
   * @param {type} curr 今回データ
   * @param {type} next 次回データ
   * @returns {StaticMap.getTooltipDirection.staticmapAnonym$9} Direction文字列、
   * オフセットxy配列
   */
  getTooltipDirectionByArrow(prev, curr, next) {

    const DIRECTIONS = ["top", "right", "bottom", "left"]
    const OFFSETS = [[0, -10], [10, 0], [[0, 10]], [-10, 0]]

    const pll = [prev["LON"], prev["LAT"]]
    const cll = [curr["LON"], curr["LAT"]]
    const nll = [next["LON"], next["LAT"]]


    const tf = new MyTurf()

    let topdir = parseInt(tf.getBearing(cll, pll) + 45)
    let tondir = parseInt(tf.getBearing(cll, nll) + 45)
    topdir = topdir < 0 ? topdir + 360 : topdir
    tondir = tondir < 0 ? tondir + 360 : tondir
    const diffdir = Math.abs(topdir - tondir)

    // p -> c -> n が直線になってない場合 //
    if (diffdir > 190 && diffdir < 170) {
      // PとNの中間点 //
      const pnc = tf.midpoint(pll, nll)
      // PN中間点からのCへの方向 //
      let arrowdir = parseInt(tf.getBearing(pnc, cll) + 45)
      arrowdir = arrowdir < 0 ? arrowdir + 360 : arrowdir

      const arrowArea = Math.floor(arrowdir / 90)


      return {
        direction: DIRECTIONS[arrowArea],
        offset: OFFSETS[arrowArea]
      }
    }
    // 直線になってる場合、線なし領域最初を返す //
    else {
      return new StaticMap().getTooltipDirection(prev, curr, next)
    }

  }


  /**
   * チャートWaypointツールチップ位置、オフセット取得
   * 線が引かれてない領域を返す
   * 
   * @param {type} prev 前回データ
   * @param {type} curr 今回データ
   * @param {type} next 次回データ
   * @returns {StaticMap.getTooltipDirection.staticmapAnonym$9} Direction文字列、
   * オフセットxy配列
   */
  getTooltipDirection(prev, curr, next) {

    const AREAS = [0, 1, 2, 3]
    const DIRECTIONS = ["top", "right", "bottom", "left"]
    const OFFSETS = [[0, -10], [10, 0], [[0, 10]], [-10, 0]]

    const pll = [prev["LON"], prev["LAT"]]
    const cll = [curr["LON"], curr["LAT"]]
    const nll = [next["LON"], next["LAT"]]

    const tf = new MyTurf()
    let topdir = parseInt(tf.getBearing(cll, pll) + 45)
    let tondir = parseInt(tf.getBearing(cll, nll) + 45)
    topdir = topdir < 0 ? topdir + 360 : topdir
    tondir = tondir < 0 ? tondir + 360 : tondir


    const pavoidArea = Math.floor(topdir / 90)
    const navoidArea = Math.floor(tondir / 90)

    // 余った領域 //
    const putAreas = AREAS.filter(n => n !== pavoidArea && n !== navoidArea)

    return {
      //topdir: topdir,
      //tondir: tondir,
      //pavoidArea: pavoidArea,
      //navoidArea: navoidArea,
      //putAreas: putAreas,
      direction: DIRECTIONS[putAreas[0]],
      offset: OFFSETS[putAreas[0]]
    }

  }

Call


    const tff = new StaticMap()
    pdatas.forEach((p, i) => {
      plls.push([p["LAT"], p["LON"]])

      if (i > 0 && i < pdatas.length - 1) {
        //console.log("-- offset result " + p["IDENT"])

        // ツールチップ配置位置、オフセット //
        const ttpdir = tff.getTooltipDirectionByArrow(pdatas[i - 1], p, pdatas[i + 1])
        //console.log(ttpdir)
        ttpdirs.push(ttpdir["direction"])
        ttpoffsets.push(ttpdir["offset"])


      }
    })

< 画面例 >

Leaflet Javascript arrowheadプラグインで矢印をつける

飛行機の飛行経路チャートで方向がわかるようにするため矢印をつけるプラグインを探してたところ、ぴったりなのが見つかり早速使ってみました。

Leaflet ArrowHead

< コード例 >

HTML


<!-- Leaflet aroowhead -->
<script src="Script/js/leaflet-arrowheads.js"></script>
<script src="Script/js/leaflet.geometryutil.js"></script>

Javascript


 const ARROWHEADS_OPTIONS = {
      size: '12px',
      yawn: 50,
      fill: true,
      //frequency: 'endonly',
      frequency: '20000m',
      offsets: {end: '12px'}
    }

 this.lines = L.polyline(plls, POLYLINE_OPTIONS).arrowheads(ARROWHEADS_OPTIONS).addTo(this.smap)

< 画面例 >

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

航空情報 2023年 7月号 [雑誌]
価格:1,425円(税込、送料無料) (2023/5/24時点)

Python 時刻形式経緯度十進変換関数クラス

航空や気象で扱われてる時刻形式の経緯度を地図で扱うために、十進値に変換する関数を作ってクラスにしてみました。

< コード例 >

クラス


'''
時刻形式経緯度十進変換クラス
'''
class DigitLL:

  '''
  コンストラクタ
  txt : 時刻経緯度
  endchr : N or S or E or W
  '''
  def __init__(self, txt, endchr):
    self.txt = txt
    self.endchr = endchr

  '''
  十進変換
  '''
  def digitLLFromTimeLL(self):

      numInt = int(self.txt)
      # 整数部
      hourPartVal = int(numInt / 10000)
      # 分
      minutePartVal = int(numInt / 100) % 100
      # 秒
      secondPartVal = numInt % 100

      minuteVal = minutePartVal / 60
      secondVal = secondPartVal / 3600
      resval = hourPartVal + minuteVal + secondVa
      
      # 南半球、西半球の場合 #
      if (self.endchr == "S" or self.endchr == "W
          resval = resval * -1

      # debug
      # print(str(hourPartVal) + ":" + str(minute

      return resval

呼び出し
ダウンロードしたExcelのファイルのセルの値を読み込んで変換する場面で使ってます。


'''
Waypointエクセルファイル読込
使用
wp_euro_procedure.pyから使う

filename : エクセルファイル名
effdate_end : effective expired date
'''
def readExcel(filename, effdate_end):

    wb = openpyxl.load_workbook(filename)
    sheet = wb["FRA Points"]

    # 書き込み用タプルリスト追加 #

    tpllist = []

    # 行数を調べる #
    rowmax = getDataRowMax(sheet)

    # identcol = "C" + str(i + 2)

    ## ++ 辞書値リスト追加走査 ++ ##
    for i in range(rowmax):

        ident = sheet["C" + str(i + 2)].value
        type = sheet["A" + str(i + 2)].value
        latcval = sheet["D" + str(i + 2)].value
        loncval = sheet["E" + str(i + 2)].value
        name = sheet["F" + str(i + 2)].value
        #if type == None:
        #    type = "FIX"
            
        type = "FIX" if type == None else type     

        latendchr = latcval[0:1]
        lonendchr = loncval[0:1]
        latvalpart  = latcval[1:]
        lonvalpart = loncval[1:]

        #lat = digitLLFromTimeLL(latvalpart, latendchr)
        #lon = digitLLFromTimeLL(lonvalpart, lonendchr)
        
        # クラスを使う #
        dll_lat = dll.DigitLL(latvalpart, latendchr)
        dll_lon = dll.DigitLL(lonvalpart, lonendchr)
        
        lat = dll_lat.digitLLFromTimeLL()
        lon = dll_lon.digitLLFromTimeLL()
        
        
        # debug
        print(ident)
        print(latcval + " => " + str(lat))
        print(loncval + " => " + str(lon))
        print("-------------------------------------------")

        tpl = {"type": "", "ident": "", "name": "", "lat": -500, "lon": -500}

        if lat != 0 and lon != 0:
            tpl["type"] = type
            tpl["ident"] = ident
            tpl["name"] = name
            tpl["lat"] = lat
            tpl["lon"] = lon
            tpllist.append(tpl)

< エクセルファイル例 >

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

航空情報 2023年 6月号 [雑誌]
価格:1,425円(税込、送料無料) (2023/5/21時点)

Javascript 時刻形式経緯度十進変換関数

気象や航空関係のデータの経緯度の形式が時刻なものを、Leafletの地図で扱う場合のデータ検証用として、
地名入力してジオコーダーを使って移動させるような処理を、入力値を時刻経緯度で行うために作ってみました。

< コード例 >


/**
 * 時刻経緯度十進変換
 * 
 * @param txt 入力文字列 ex. 353220.86N
 *                            1394906.93E
 */
const getDecLocationFromTimeLocation = (txt) => {

  let res = ""

  const pos_comma = txt.indexOf(".")

  const txt_int_part = txt.slice(0, pos_comma)

  let txt_dec_part = txt.slice(pos_comma + 1)
  txt_dec_part = txt_dec_part.replace("N", "").replace("S", "").replace("W", "").replace("E", "")

  let txt_int = ""
  let txt_min = ""

  //alert(txt_int_part + "\n" + txt_int_part.length)

  // 整数部分、分の切り取り //
  switch (txt_int_part.length) {
    // 整数1桁 //
    case 5:
      txt_int = txt_int_part.slice(0, 1)
      txt_min = txt_int_part.slice(1, 3)
      break
      // 整数2桁 //  
    case 6:
      txt_int = txt_int_part.slice(0, 2)
      txt_min = txt_int_part.slice(2, 4)
      break;
      // 整数3桁 //  
    case 7:
      txt_int = txt_int_part.slice(0, 3)
      txt_min = txt_int_part.slice(3, 5)
      break
  }

  // 秒は常に右2文字
  const txt_sec = txt_int_part.slice(-2)

  //alert (txt_int + ":" + txt_min + ":" + txt_sec + "." + txt_dec_part)


  res = Number(txt_int) +
          Number(txt_min / 60) +
          Number(txt_sec / 3600) +
          Number(txt_dec_part / 360000)

  // 南半球の場合 //
  if (txt.slice(-1) === "S" || txt.slice(-1) === "W") {
    res = res * -1
  }

  return res
}
           

作ってから気づいたのですが、整数部分の分秒の変換は、Pythonでの例ですが、


numInt = int(self.txt)
# 整数部
hourPartVal = int(numInt / 10000)
# 分
minutePartVal = int(numInt / 100) % 100
# 秒
secondPartVal = numInt % 100
minuteVal = minutePartVal / 60
secondVal = secondPartVal / 3600
resval = hourPartVal + minuteVal + secondVal

のようにすれば簡潔になります。

< 利用例 >

AiP JAPANに載っている FIXのWaypoint EPSON がどこにあるか調べる

千葉県にあることがわかる

MySQL 時刻形式経緯度十進変換関数

ページスクレープしたテキストをデータベースに登録する為、seleniumやsoupを使えば、とったテキストが整ってる場合は、自分のプログラムに関数を入れ処理出来ますが、コピペしてエクセルに貼り付けてSQLにしないと出来ないような整理されてないソースだったので、MySQLのUDFで作ってみました。

CREATE DEFINER=`root`@`localhost` FUNCTION `fc_decLL_from_timeLL`(
	`srctxt` VARCHAR(20)
)
RETURNS double
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT '時刻経緯度十進変換関数'
BEGIN

DECLARE resLL DOUBLE DEFAULT 0;      -- decimaled return value 

DECLARE pos_comma TINYINT;           -- chart index of .
DECLARE txt_int_part VARCHAR(7);     -- always 7char or 6char
DECLARE txt_dec_part VARCHAR(2);     -- decimal part text
DECLARE chr_nesw CHAR(1);            -- north or east or south or west 

DECLARE txt_int VARCHAR(3);          -- 2 char or 3 char
DECLARE txt_min CHAR(2);             -- always 2 char
DECLARE txt_sec CHAR(2);             -- always 2 char


SET pos_comma = INSTR(srctxt, '.');
SET txt_int_part = LEFT(srctxt, pos_comma - 1);

SET txt_dec_part = SUBSTR(srctxt, pos_comma + 1, pos_comma + 4);
SET txt_dec_part = TRIM('N' FROM txt_dec_part);
SET txt_dec_part = TRIM('S' FROM txt_dec_part);
SET txt_dec_part = TRIM('W' FROM txt_dec_part);
SET txt_dec_part = TRIM('E' FROM txt_dec_part);

-- 整数部分、分の切り取り
IF LENGTH(txt_int_part) = 5 THEN
  SET txt_int = LEFT(txt_int_part, 1);
  SET txt_min = MID(txt_int_part, 2, 2);
ELSEIF LENGTH(txt_int_part) = 6 THEN
  SET txt_int = LEFT(txt_int_part, 2);
  SET txt_min = MID(txt_int_part, 3, 2);
ELSEIF LENGTH(txt_int_part) = 7 THEN
  SET txt_int = LEFT(txt_int_part, 3);
  SET txt_min = MID(txt_int_part, 4, 2);
END IF;

-- 秒は常に右2文字
SET txt_sec = RIGHT(txt_int_part, 2);

SET chr_nesw = RIGHT(srctxt, 1);

SET resLL = CAST(txt_int AS UNSIGNED) + 
            CAST(txt_min AS UNSIGNED) / 60 +
            CAST(txt_sec AS UNSIGNED) / 3600 +
            CAST(txt_dec_part AS UNSIGNED) / 360000;

-- 南半球、西半球の場合            
IF (chr_nesw = 'S' OR chr_nesw = 'W') THEN
  SET resLL = resLL * -1;
END IF;            
 
-- debug
-- INSERT INTO tst (fld0, fld1) VALUES(txt_int_part, txt_dec_part);
-- INSERT INTO tst (fld0, fld1, fld2) VALUES(txt_int, txt_min, txt_sec);

  
RETURN resLL;  
  
END

< テキストソース例 >

< Excel貼付例 >