Islanti Thingvellir

  Pääsivulle  |  TIETOTEKNIIKAN PÄÄSIVULLE

Koordinaattien sijaintimaa

Seuraavassa esitetään funktio etsiMaa(..), joka tutkii ovatko annetut koordinaatit Suomessa vai ei. Tietoa ei kysellä ensisijaisesti netistä, vaan verrataan koordinaatteja Suomen sisäpuolen karkeasti määritteleviin koordinaattiarvoihin, ja toisaalta suorakaiteeseen jonka sisään Suomi mahtuu kokonaan

Lopussa esitetään luokka NettiKysely, jota funktio etsiMaa tarvitsee. Lisäksi siellä on luokka Reachability jonka avulla voidaan tehokkaasti selvittää onko meillä nettiyhteyttä vai ei.


//--------------------------------------------
// 2.2.2018  M27
// Missä maassa koordinaatit ovat
// Output  "Suomi", "Muu"
//       "" ei selvinnyt eli joko ei nettiä tai googlen palvelu ei vastannut
// Maa selvitetään ensisijaisesti oheisten koordinaattien avulla:
// - Suomi suorakaiteen sisällä: ulkopuolinen alue on "Muu"
// - Suomen sisäpuoli (alla taulukko luvut): sisäpuolinen alue on "Suomi"
// Em. koordinaattien välinen alue tutkitaan netistä Google-palvelun avulla: onko inputtina
// olevat koordinaatit Suomea vai ei, tulos "Suomi", "Muu" tai ""
// Vastaava Openstreet-kysely on epäluotettavampi (usein hidas ja tulee timeout)
// Input lati ja longi oltava oheisessa muodossa:
// let maaz = Meka.etsiMaa(lati: "60.582535", longi: "25.682804")
// Täällä kutsutaan funktiota jossa on trailing closure ja kutsuva koodi jatkaa ohi
class func etsiMaa(lati laz: String, longi lonz: String) -> String
{
    if laz.isEmpty || lonz.isEmpty
    {
        print("Meka M27-01: input tyhjä")
        return ""
    }
    var maaz = ""
    // Suomen sisäpuoli on "viipaloitu vaakasuoraan"
    // Vaakasuora latitude-viiva, pystysuorat rajat logi vasen ja longi oikea:
    // vaaka[i], lonvas[i], lonoik[i]
    // lati, vaakasuoran viivan vasen reuna (lonvas), oikea reuna (lonoik)
    let luvut =
    [
        59.77325, 21.80186,     23.58164,
        59.92775, 19.94517,     24.72422,
        60.15270, 19.45078,     25.95469,
        60.34352, 19.36289,     27.65757,
        60.54943, 19.91221,     27.75645,
        60.68419, 20.97788,     27.96519,
        61.32852, 21.27451,     29.15171,
        62.69959, 20.67257,     31.31554,
        63.35327, 20.96920,     30.75787,
        63.69035, 22.30675,     29.97521,
        64.74155, 23.94419,     29.62364,
        65.52683, 24.96544,     29.52477,
        65.91980, 24.11949,     29.75548,
        66.36346, 23.77057,     29.48346,
        66.80510, 24.04259,     28.81733,
        68.40746, 23.53290,     28.50567,
        68.54852, 25.18085,     28.27900,
        69.02590, 25.79411,     28.36766,
        69.27517, 25.75291,     28.84556,
        69.58398, 25.96440,     28.98907,
        69.88141, 26.46625,     28.14716
    ];
    var vaaka = [Double]()
    var lonvas = [Double]()
    var lonoik = [Double]()
       
    var i = 1
    for luku in luvut
    {
        if i == 3
        {
            lonoik.append(luku)
            i = 1
        }
        else if i == 2
        {
            lonvas.append(luku)
            i = 3
        }
        else // if i == 1
        {
            vaaka.append(luku)
            i = 2
        }
    }
    if 3 * vaaka.count != luvut.count
    {
        print("Meka M27-05: taulut epäonnistuivat")
        return ""
    }
    let latd = Double(laz) ?? 0.0   // input muutetaan String -> Double
    let lond = Double(lonz) ?? 0.0
   
    if (latd < 59.65135 || latd > 70.15173)  // Suomi kokonaan tämän suorakaiteen sisällä
    {
        return "Muu";
    }
    if (lond < 18.87949 || lond > 31.71152)
    {
        return "Muu";
    }
    if (latd >= vaaka[0] && latd <= vaaka[vaaka.count - 1])
    {
        for i in 0..<(vaaka.count - 1)
        {
            if (latd >= vaaka[i] && latd < vaaka[i+1])
            {
                // Kun piste on vaakasuorien vakioviivojen välissä, tehdään
                // uusi vaakaviiva pisteen kautta, päät lineaarisesti ylä- ja alapuolten päiden suhteen
                let kerr = (latd - vaaka[i]) / (vaaka[i+1] - vaaka[i]);
                let lonva = lonvas[i] + (lonvas[i+1] - lonvas[i]) * kerr;
                let lonoi = lonoik[i] + (lonoik[i+1] - lonoik[i]) * kerr;
                if (lond >= lonva && lond <= lonoi)
                {
                    return "Suomi";
                }
            }
        }
    }

    // Kun ei löytynyt Suomea eikä varmuudella Muuta, niin pitää etsiä seuraavaksi netistä
    let semaphore = DispatchSemaphore(value: 0)
    let nky = NettiKysely()   // luokan määrittely jäljempänä
    nky.latiz = laz
    nky.longiz = lonz
       
    // Nettikysely vie aikaa, sovellus ei jää odottamaan, eli homma jatkuu
    // tämän sulkuryhmän jälkeen suoraan ja sulkuryhmä valmistuu "joskus"
    // Nyt on jäätävä odottamaan, siksi semaphore
    nky.haeData        // This method has a trailing closure
        {  tuloz in
            if tuloz == "Virhe"   // ei esim. saatu yhteyttä nettiin
            {
                print("Meka M27-05: maan nettikysely epäonnistui")
                maaz = ""
            }
            else if tuloz == "ei Suomi"
            {
                print("Meka M27-06: ei olla Suomessa")
                maaz = "Muu"
            }
            else
            {
                print("Meka M27-07: Suomessa ollaan")
                maaz = "Suomi"
            }
            semaphore.signal()            // nyt semaforilta saa jatkaa matkaa
        }
    semaphore.wait()        // pyydetään prosessia odottamaan semaforille
    print("Meka M27-09: Maa = \(maaz)")
    return maaz
}


*************************************************************
// Luokassa käsitellään nettiin menevä kysely koordinaattien maasta Suomi / ei Suomi.
// Funktio haeData() tekee asynkronisen kyselyn
// Kutsujan on hoidettava, että latiz ja longiz saavat arvon

// Jotta allaoleva http-nettikysely onnistuisi, on info.plist täydennettävä:
//    <key>NSAppTransportSecurity</key>
//    <dict>
//    <key>NSAllowsArbitraryLoads</key><true/>
//    </dict>

class NettiKysely: NSObject
{
    var urliz = ""
    var latiz = ""
    var longiz = ""
    var paikkaz = ""
   
    // ----------------------------------------------------
    // 14.12.2017  M33
    // the completion closure signature is (NSString) -> ()
    // Selvitetään onko (latiz, longiz) Suomessa vai ei,
    // kysytään GoogleMapistä (OpenStreetMap oli epävarmempi)
    // Huom! Kutsuvassa paikassa tarkistettava että lngz ja latz eivät ole null tai tyhjiä
    // Output on "ei Suomi", "Suomi" tai "Virhe"
    func haeData(_ completion: @escaping (NSString) -> ())
    {
        // Allaoleva Google-palvelu ei toimi suoraan, vaan tarvitset Googlelta api-tunnuksen
        // antamalle heille luottokorttisi tiedot:
        urliz = "http://maps.googleapis.com/maps/api/geocode/json?latlng=\(latiz),\(longiz)&sensor=true"
        // Tämä OpenStreet-palvelu toimii, jos käyttäjä on identifioitu. Omassa sovelluksessa voi
        // käyttää omaa sähköpostiosoitetta:
        urliz = "https://nominatim.openstreetmap.org/reverse?email=ETUNIMI.SUKUNIMI@byethost.com&format=xml&lat="
        + latiz + "&lon=" + longiz + "&zoom=18&addressdetails=1";
        if let url = URL(string: urliz)
        {
            let session = URLSession.shared  // tämä on asynkroninen
            let task = session.dataTask(with: url, completionHandler:
            {
                data, response, error in
                if let data = data, let jsonString = String(data: data, encoding: String.Encoding.utf8), error == nil
                {
                    let suomiz = "Suomi"   
                    var tuloz = "ei Suomi"
                    if jsonString.range(of: suomiz) != nil
                    {
                        tuloz = "Suomi"
                    }
                    else if jsonString.range(of: "Finland") != nil
                    {
                        tuloz = "Suomi"
                    }
                    completion(tuloz as NSString)
                }
                else
                {
                    print("Meka M33-01: error=\(error!.localizedDescription)")
                    completion("Virhe")
                }
            })           

            task.resume()
        }
        else
        {
            print("Meka M33-03: url on virheellinen")
            completion("Virhe")
        }
    }
}

*************************************************************
// 30.1.2018
// Tämän avulla voidaan tutkia onko nettiyhteyttä
// https://stackoverflow.com/questions/25623272/how-to-use-scnetworkreachability-in-swift
// Käyttötapa:
// if !Reachability.shared.isConnectedToNetwork() { // ei nettiä }
import SystemConfiguration

final class Reachability
{
    private init () {}
    class var shared: Reachability
    {
        struct Static
        {
            static let instance: Reachability = Reachability()
        }
        return Static.instance
    }
   
    func isConnectedToNetwork() -> Bool
    {
        guard let flags = getFlags()
            else
            {
                return false
            }
        let isReachable = flags.contains(.reachable)
        let needsConnection = flags.contains(.connectionRequired)
        return (isReachable && !needsConnection)
    }
   
    private func getFlags() -> SCNetworkReachabilityFlags?
    {
        guard let reachability = ipv4Reachability() ?? ipv6Reachability()
            else
            {
                return nil
            }
        var flags = SCNetworkReachabilityFlags()
        if !SCNetworkReachabilityGetFlags(reachability, &flags)
        {
            return nil
        }
        return flags
    }
   
    private func ipv6Reachability() -> SCNetworkReachability?
    {
        var zeroAddress = sockaddr_in6()
        zeroAddress.sin6_len = UInt8(MemoryLayout<sockaddr_in>.size)
        zeroAddress.sin6_family = sa_family_t(AF_INET6)
       
        return withUnsafePointer(to: &zeroAddress,
            {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1)
            {
                SCNetworkReachabilityCreateWithAddress(nil, $0)
            }
            })
    }
   
    private func ipv4Reachability() -> SCNetworkReachability?
    {
        var zeroAddress = sockaddr_in()
        zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
        zeroAddress.sin_family = sa_family_t(AF_INET)
       
        return withUnsafePointer(to: &zeroAddress,
            {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1)
            {
                SCNetworkReachabilityCreateWithAddress(nil, $0)
            }
            })
    }
}



--------------------------------
(sivua muokattu  8.4.2020)