Yleistä
Avoimen datan idea, noin yleisellä tasolla, lienee, että yhteisillä rahoilla (~verorahoilla) hankittu tieto pitäisi olla myös yhteisessä käytössä ilman mitään (merkittäviä) rajoituksia tai lisäkustannuksia. Nykyisessä maailmantilanteessa pitää lisäksi tyytyväisenä huomioida, että aivan kaikki verorahat eivät ole (vielä) menneet Ukrainaan tai USA:sta tehtäviin asehankintoihin. Jätetään politikointi kuitenkin (tällä kertaa) tähän ja siirrytään asiaan.
Ilmatieteen laitos
Ilmatieteen laitoksella on tarjolla paljon avointa dataa, tosin sen (laajempi) hyödyntäminen vaatii hieman vaivannäköä. Ei se kuitenkaan mitenkään ylivoimaista ole. Ilmatieteen laitoksen verkkosivuilla on hyvät ohjeet datan lataamiseen ja Githubista löytyy Python rajapinta tiedon hakemiseksi, hyödynnetään sitä.
Auringon säteilyteho
Näin pakkaskelillä ja sähkön hinnan ollessa taivaissa, energia-asiat ovat pinnalla. Katsotaan kevättä odotellessa auringon säteilytehoa (tai sen puutetta) Ilmatieteen laitoksen avoimesta datasta.
Python-ohjelma
Ohjelma toimii Googlen Colab'issa, joka on niitä Googlen vähemmän tunnettuja palveluja. Colab sisältyy kuitenkin samaan (perus)pakettiin, kuin Gmail ja Drive eli se on monilla jo valmiina odottamassa käyttöä. Colabia käytettäessä omalle koneelle ei tarvitse tehdä mitään asennuksia tai muutoksia eli käyttö pitäisi onnistua myös "tietoturvallisella" työkoneella.
Colab on Googlen palvelimessa toimiva Jupyter Notebook, jossa on mm. Python 3-kehitysympäristö.
Python-ohjelma hakee Jyväskylän lentoaseman mittauspisteen tuloksista Auringon kokonaissäteilytehon viimeisen vuorokauden ajalta ja viiden minuutin mittausvälein. Tuloksista piirretään kuvaaja, jonka vaaka-akselilla on aika minuutteina tietojen lataamishetkestä ja pystyakselilla kokonaissäteilyteho.
# !pip install fmiopendata tarvitaan vain ensimmäisellä kerralla list = !pip list if len(list.grep("fmiopendata")) == 0: !pip install fmiopendata from IPython.display import clear_output clear_output() import datetime as dt from fmiopendata.wfs import download_stored_query import re # Retrieve the latest 24 hour of data end_time = dt.datetime.utcnow() start_time = end_time - dt.timedelta(hours=24) # Convert times to properly formatted strings start_time = start_time.isoformat(timespec="seconds") + "Z" end_time = end_time.isoformat(timespec="seconds") + "Z" sunrad = download_stored_query("fmi::observations::radiation::multipointcoverage", \ args=["parameters=GLOB_1MIN", "fmisid=101339", \ "starttime=" + start_time, "endtime=" + end_time, \ "timestep=5"]) rawdata = sunrad.data place = "Jyväskylä lentoasema" meas = "Global radiation" radiation = [] cntr = 0 sum = 0 min = 9999 max = -9999 for time in rawdata: value = rawdata[time][place][meas]['value'] units = rawdata[time][place][meas]['units'] radiation.append([time.isoformat(), value, units]) if value == value: # NaN check cntr += 1 sum += value if min > value: min = value if max < value: max = value radiation.sort() print("\n{:<20}{:>7}".format("Arvoja (ei Nan): ", cntr)) print("{:<20}{:>7.1f}{}".format("Keskiarvo: ", sum/cntr, " W/m2")) print("{:<20}{:>7.1f}{}".format("Min: ", min, " W/m2")) print("{:<20}{:>7.1f}{}\n".format("Max: ", max, " W/m2")) print("{:<20}{}".format("Alkuaika: ", start_time)) print("{:<20}{}\n".format("Loppuaika: ", end_time)) # Säteilytehon aikajakauma import matplotlib.pyplot as plt %matplotlib inline endtm = dt.datetime.fromisoformat(end_time[0:-1]) time = [] power = [] for meas in radiation: tmd = dt.datetime.fromisoformat(meas[0]) - endtm tmin = round((tmd.total_seconds())/60, 0) # Aikaero minuutteina time.append(tmin) power.append(meas[1]) plt.plot(time, power) plt.show()
Tulokset
Ja tälläiset tulokset saatiin talvisena pakkaspäivänä, kun sitä (lämmitys)energiaa oikeasti tarvittaisiin. Ei tuo nyt kovin lupaavalta näytä. Ainakaan, jos vertaa sitä poliitikkojen puheisiin Suomen tulevaisuudesta (halvan) uusiutuvan energian suurvaltana.
Maanmittauslaitos
Maanmittauslaitos tarjoaa myös avointa dataa, mutta enimmäkseen ladattavina tiedostoina. Kartta-aineistossa reaaliaikavaatimukset lienevät hieman vähäisemmät kuin esim. säätiedoissa ja ladattavat tiedostot ajavat asiansa ihan hyvin.
Kokeillaan korkeustietoa 2m rasterilla ja ASCII Grid-muodossa, joka on selväkielisenä helposti (?) hyödynnettävissä. Korkeusmallin karttalehtijako on 6 x 6 km, joten mittauspisteitä yhdellä karttalehdellä on 9 miljoonaa ja tiedostokoko noin 65 MB. Kun kohdealue sattui vielä melkein karttalehtijaon risteykseen, niin tiedostoja tarvitaan neljä, yhteensä 260 MB! Entä sitten? Sitä vartenhan tietokoneet ovat, että ne voivat käsitellä suuriakin tietomääriä.
Korkeustiedostossa on ensi muutama rivi otsikkotietoja, joissa on esim. vasemman alanurkan paikka, mittauspisteiden jako (2m) ja mittauspisteiden määrä (3000x3000). Otsikkotietojen jälkeen tulee varsinainen korkeustieto alkaen vasemmasta ylänurkasta (länsi-pohjoinen) edeten oikealle kohti itää. Ja sitten seuraava rivi kohti etelää jne. tiedoston loppuun asti.
Paikkatietojen koordinaatisto on suomalainen ETRS TM35FIN, joka on poikittainen Mercatorin-projektio (Gauss-Kruger) keskimeridiaanilla 27ast itäistä pituutta (katsoin tämän Wikipediasta). Tuo kuulostaa jo aika vaikealta, mutta se on vielä helppoa verrattuna siihen laskentaan, jolla WGS84-koordinaatit (esim. GPS ja ~Google Maps) muutetaan ETRS TM35FIN-koordinaateiksi.
Tuo koordinaatistomuunnos vaikuttaa niin vaikealta, että pitää ottaa tekoäly avuksi. (Sainpas mahdutettua "tekoälyn" näillekin sivuille ja nyt sivuston hype-kerroin on kunnossa.) Sen verran perustelua tuohon "tekoäly"-termiin, että koneoppiminen on (pieni) osa tekoälyä ja tarkoitus on käyttää koordinaatistomuunnoksessa opetusdatalla tehtyä koneoppimista.
WGS84 -> ETRS TM35FIN koneoppimisella
Kohdealueena on korkeusmallin karttalehdet M4211G, M4211H, M4213A ja M4213B, joiden keskikohta on 6816000, 332000 (ETRS TM35FIN). Alue on 12 x 12 km, josta otetaan opetusdataksi pisteet tasajaolla kahden kilometrin välein. Yhteensä 49 pistettä (7 x 7). Näille pisteille haetaan vastaavuus WGS84 <-> ETRS TM35FIN esim. Maamittauslaitoksen karttapaikan koordinaatistomuunnoksella.
Koneoppiminen (opetettu):
- Lähtötiedoiksi tarvitaan opetusdata.
-
Arvataan mallin tyyppi:
- Muuttujien ja tulosten määrä. Tässä 2 -> 2.
- Mallin asteluku. Ensimmäisen asteen (lineaarinen) malli olisi yksinkertaisin, mutta pallopinnan kaarevat leveys- ja pituuspiirit aiheuttavat virhettä. Otetaan toisen asteen malli.
- Otetaan laskentakoordinaatiston nollapisteiksi kohdealueen keskikohta. Tämä yksinkertaistaa mallia ja vähentänee (?) virhettä, kun käsitellään vain (suhteellisen) lyhyitä etäisyyksiä ja mallin keskikohta voidaan pakottaa nollaksi (bias/intercept = 0).
Ohjelman toiminta:
- Luetaan opetusdata tiedostosta.
- Nollataan opetusdatan keskikohta.
- Muodostetaan (koneoppimisella) toisen asteen malli koordinaatistomuunnoksesta.
- Tallennetaan laskentamallin vakiot tiedostoon.
-
Tehdään laskentamallin virhearvio (opetusdatalla).
- Toisen asteen mallilla virhealueeksi saatiin: -0.017...0.015 m, joka on hyvinkin riittävä tähän käyttötarkoitukseen.
- Vertailun vuoksi ensimmäisen asteen mallin virhealue oli n. -10...10 m.
# Koneoppimisella tehty malli koordinaattimuutokseen
# WGS84 -> ETRS-TM35FIN
import os
import numpy as np
import re
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
import json
file = "Dataset_HV.csv"
if not os.path.isfile(file):
!tar -xvf /content/drive/MyDrive/zColab_data/Data.tar
# Opetusdata:
# North, east, latitude, longitude
with open(file, encoding="utf-8") as f:
f.readline() # Otsikkorivejä ei tarvita
f.readline()
lines = f.readlines()
data = []
for line in lines:
tmp = re.split(";", line)
data.append([
float(tmp[0]),
float(tmp[1]),
float(tmp[2]),
float(tmp[3])
])
dataset = np.array(data)
# Tekee koordinaattimuunnosmallin ja tallentaa sen json-tiedostoon.
# Keskimmäiset arvot
north_m = np.median(dataset[:,0])
east_m = np.median(dataset[:,1])
for row in dataset:
if row[0] == north_m:
if row[1] == east_m:
ZeroCoords = row
break
# Nollaus keskikohtaan
dataZero = dataset - ZeroCoords
# Datat mallin opetusta varten
x = dataZero[:,2:4] # Latitude, longitude
y = dataZero[:,0:2] # North, east
# Toisen asteen malli, kaksi muuttujaa, nollapisteen kautta
polyfeatures = PolynomialFeatures(degree=2, include_bias=False)
x_poly = polyfeatures.fit_transform(x)
model = LinearRegression(fit_intercept=False)
model.fit(x_poly, y)
# Tallennetaan mallin laskentavakiot tiedostoon
wgs84_tm35fin = {
"ZeroCoords": np.ndarray.tolist(ZeroCoords),
"Coef": np.ndarray.tolist(model.coef_)
}
with open("wgs84_tm35fin.json", "w", encoding="utf-8") as f:
f.write(json.dumps(wgs84_tm35fin))
# Virhearvio
test = model.predict(x_poly)
virhe = test - dataZero[:,0:2]
print("\nMallin virhe opetusdatalla:\n")
print("{:3.3f}{}{:3.3f}{}".format(virhe.min(), "...", virhe.max(), " m\n"))
Reitin korkeustieto
Kokeillaan edellä tehtyä laskentamallia ja MML:n korkeusmallitiedostoja reitin korkeusprofiilin muodostamiseen. Lähtötiedoksi piirustellaan Google Maps'issä reitti ja tallennetaan se csv-tiedostoksi. Tiedostossa koordinaatit ovat ~WGS84.
Ohjelman toiminta:
- Luetaan reitti- ja korkeusdata-tiedostot.
- Muutetaan reitin koordinaatit WGS84 -> ETRS TM35FIN.
- Lasketaan reitistä kuljettu matka.
- Haetaan reittipisteille korkeustieto.
- Piirretään tuloksista korkeusprofiili.
Ohjelman osat:
- Lähtötietojen haku
-
Pääohjelma
- Koordinaatistomuunnos
-
Korkeustiedon haku
- Korkeusdata-tiedoston haku
# Käytetty reittidata on tallennettu Google Mapsistä .csv-muodossa.
import os
import re
import numpy as np
import json
import glob
# Korkeusdata
if not os.path.isfile("M4211G.asc"):
!tar -xvf /content/drive/MyDrive/zColab_data/Elevations.tar
base_dir = '/content/'
files = glob.glob(base_dir + 'M42*') # Datatiedostot
# Reitti
file = "juoksulenkki.csv"
if not os.path.isfile(file):
!tar -xvf /content/drive/MyDrive/zColab_data/lenkki.tar
with open(file, encoding="utf-8") as f:
rawdata = f.readline() # Ensimmäistä riviä ei tarvita
rawdata = f.readline() # Reitti on toisella rivillä
res = re.search("LINESTRING \((.*)\)\"", rawdata) # LINESTRING sisältö
lines = re.split(",", res.group(1)) # Jaetaan pilkun kohdalta
data = []
for line in lines:
tmp = re.search("^\s*(.*)", line) # Välilyönnit pois alusta
tmp = re.split("\s", tmp.group(1)) # Jaetaan välilyönnin kohdalta
data.append([
float(tmp[1]),
float(tmp[0])
])
gpsroute = np.array(data) # Reitti: latitude, longitude
# Koordinaatistomuunnos WGS84 -> ETRS TM35FIN
def wgs84_tm35fin(route):
# Muunnoksen vakioparametrit
jsonfile = "wgs84_tm35fin.json"
with open(jsonfile, encoding="utf-8") as f:
parms = json.loads(f.read())
Zero = np.array(parms["ZeroCoords"])
Coef = np.array(parms["Coef"])
# Laskennan nollapiste
inp = route - Zero[2:4]
# Laskennalliset lisäparametrit (toisen asteen malli)
la = inp[:,0]
lo = inp[:,1]
tmp = la ** 2 # Latitude ^2
inp = np.hstack((inp, np.atleast_2d(tmp).T))
tmp = la * lo # Latitude * longitude
inp = np.hstack((inp, np.atleast_2d(tmp).T))
tmp = lo ** 2 # Longitude ^2
inp = np.hstack((inp, np.atleast_2d(tmp).T))
outp = np.matmul(inp, Coef.T)
# Nollapisteen palautus
outp = outp + Zero[0:2]
return outp
# Hakee paikalle datatiedoston
def getFile(point):
north = point[0]
east = point[1]
headers = {}
for file in files:
with open(file, encoding="utf-8") as f:
i = 0
head = {}
while i < 6: # Luetaan otsikkotiedot
line = re.split("\s+", f.readline())
head[line[0]] = float(line[1])
i += 1
headers[file] = head
datafile = ""
for file in headers:
xl = headers[file]["xllcorner"]
yl = headers[file]["yllcorner"]
if ((xl <= east) and \
(east < (xl + headers[file]["ncols"] * headers[file]["cellsize"])) and \
(yl <= north) and \
(north < (yl + headers[file]["nrows"] * headers[file]["cellsize"]))):
datafile = file
break
return datafile
# Lukee datatiedoston tiedot
def readData(datafile):
with open(datafile, encoding="utf-8") as f:
i = 0
header = {}
while i < 6: # Luetaan otsikkotiedot
line = re.split("\s+", f.readline())
header[line[0]] = float(line[1])
i += 1
ascdata = f.readlines() # Luetaan datarivit
data = []
for line in ascdata:
ascrow = re.split("\s+", line)
row = ascrow[1:-1] # Alussa ja lopussa on tyhjä
data.append(row)
return {"Header": header, "Data": data}
# Hakee paikan korkeuden tiedostosta
def getElevation(route):
ele = []
prevdatafile = " "
for point in route:
north = point[0]
east = point[1]
datafile = getFile(point)
if datafile != prevdatafile:
print("Datafile: ", datafile)
prevdatafile = datafile
if len(datafile) > 0:
datadict = readData(datafile)
header = datadict["Header"]
data = datadict["Data"]
else: # Datatiedostoa ei löytynyt
ele.append(-9999.0)
continue
r = (north - header["yllcorner"]) / header["cellsize"]
r = int(header["nrows"] - r - 1)
c = int((east - header["xllcorner"]) / header["cellsize"])
ele.append(float(data[r][c]))
return ele
# Pääohjelma
# Koordinaatistomuunnos
route = wgs84_tm35fin(gpsroute)
# Matka
delta = route[1:,:] - route[:-1,:]
tmp = delta ** 2
tmp = tmp[:,0] + tmp[:,1]
dist = tmp ** (1/2) # Mittauspisteiden välimatka
odo = [0]
sum = 0
for d in dist:
sum += d
odo.append(sum) # Kokonaismatka
# Korkeudet
ele = getElevation(route)
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(odo, ele)
plt.xlabel("Matka / m")
plt.ylabel("Korkeus / m")
plt.show()
Ja tässä tulos lenkkimaastosta.