The Haversine formula

在我們設計App時,有時候我們會需要獲得使用者的地理位置資訊
然後用使用者目前的經緯度座標,計算離使用者地點最近的幾個地點
這時候如果你的資料庫中有存放一些地點的座標資料
那我們就可以使用所謂的Haversine公式,來算出地球上使用者與這些地點座標之間的距離


(簡單的解釋一下每個符號的意義:d是距離,r是弧度,φ是經度,λ是緯度)
然後假設我們資料庫中有一個資料表(table)
且資料表中的每個欄位記錄了每個地點的名稱(name)與座標(lat, lon),例如
table
name
lat
lon
name_1
latitude_1
longitude_1
name_2
latitude_2
longitude_2
...
...
...

接著我們可以將上面的公式轉換一下並且假設我們現在的座標在(23, 120)
在資料庫中就可以用這個語法來查詢
SELECT
     name,
     ( 6371 * acos( cos( radians( 23 ) ) * cos( radians( lat ) ) * cos( radians( lon ) 
     - radians( 120 ) ) + sin( radians( 23 ) ) * sin( radians( lat ) ) ) ) AS distance
FROM
     table
ORDER BY
     distance   
結果就會顯示這些地點的名稱與使用者之間的距離(公里),並且已經依照遠近排序
其中用到的acos()、cos()、sin()、radians()等函數,在MySQL中都可以直接使用

Reference


Haversine formula - Wikipedia, the free encyclopedia
Creating a Store Locator with PHP, MySQL & Google Maps - Google Maps API — Google Developers

Public variables and methods

最近上數位學習的課,正在討論SCORM(Sharable Content Object Reference Model)
SCORM真的是個很神奇的傢伙!
它可以壓縮成package,接著上傳Moodle
然後學習者的一舉一動都會被監視XD
(例如:學習後進行測驗的分數、已經進行學習與否還有學習所耗費的時間等等)

其中談到SCORM的架構時,有說到SCORM使用了標準(standard)
來決定要儲存學習者的哪些資訊
也就是追蹤學習者學習的最小單位(SCOs)所用到的技術

在這過程中不小心就聯想到public變數與method的使用XD
所以就來複習一下吧 :P  這次要探討的是

為什麼在修改資料時,與其直接操作public變數,另外建立method反而是更好的做法?

以下我將用箱子還有球來示範,應該是夠淺顯易懂吧~

使用public變數

public class Box(){
     //假設我們找來了一個箱子(它是一個物件)
     //並且在裡面放進了5顆球,想說等一下要分別發送給5個人
     int ball = 5;
}
然後...有人看到了這個箱子
Box box = new Box();
box.ball = 0;                     //噢不...球球被拿光光了T_T

然後看看這個方式

public class Box(){
     //一樣放進5顆球
     private int ball = 5;
     public Box()

     //規定一次只能放一顆
     public void getOnlyOneBall(){
          ball = ball - 1;
     }
}
之後有人要拿
Box box = new Box();
box.getOnlyOneBall();  //一次只能拿一顆吧!你看看你~
Yes! 這次狀況就好很多了。
所以說呢可以使用一些特別的方法(method),避免有心人士一次就把球球拿光光
或是在不小心的狀況之下本來要放4顆,結果放進了10顆(意外對變數造成修改)

在大多數情況下,我們都希望能夠自由地對變數進行操作。但是誠如Clean Code無瑕的程式碼一書所說:
「將實現的過程隱藏實際上便是一種抽象化的過程。而在這當中,類別不單單只是透過讀取及設定函式讓變數供人存取而已;To be more specific, 它提供了一個抽象介面,讓使用者在不需要知道實現過程的狀態下,還能操縱資料的本質。」
改變世界的九大演算法一書中也有提到:
「撰寫程式碼的原則是盡量隱藏資料。」

Fragment切換,另一種方法

在之前的文章「Fragment切換起手式」中我已經介紹了最基本的方法,就是透過Framelayout作為container
然後使用FragmentTransaction的方式來更換Fragment
其實還存在另外一種方法,這邊我將使用Facebook的一個開放原始碼project來示範

main.xml
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

    <fragment android:name="com.facebook.scrumptious.SelectionFragment"
              android:id="@+id/selectionFragment"
              android:layout_width="match_parent"
              android:layout_height="match_parent" />

    <fragment android:name="com.facebook.scrumptious.SplashFragment"
              android:id="@+id/splashFragment"
              android:layout_width="match_parent"
              android:layout_height="match_parent" />

    <fragment android:name="com.facebook.widget.UserSettingsFragment"
              android:id="@+id/userSettingsFragment"
              android:layout_width="match_parent"
              android:layout_height="match_parent" />

</LinearLayout>
一開始先將所有Fragment的layout都放置在主要Activity的layout中
接著透過hide與show的方式控制Fragment出現的時機

MainActivity.class
        FragmentManager fm = getSupportFragmentManager();
        SplashFragment splashFragment = (SplashFragment) fm.findFragmentById(R.id.splashFragment);

        // fragments[畫面名稱]是個用來存放相關畫面的Class類別陣列
        // 如SPLASH、SELECTION、SETTINGS分別代表登入頁、主選單與設置頁
        fragments[SPLASH] = splashFragment;
        fragments[SELECTION] = fm.findFragmentById(R.id.selectionFragment);
        fragments[SETTINGS] = fm.findFragmentById(R.id.userSettingsFragment);

        FragmentTransaction transaction = fm.beginTransaction();
        for(int i = 0; i < fragments.length; i++) {
            transaction.hide(fragments[i]);

        }
        transaction.commit();
...
private void showFragment(int fragmentIndex, boolean addToBackStack) {
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction transaction = fm.beginTransaction();

        //將不需要的頁面隱藏,只顯示想展示的頁面
        for (int i = 0; i < fragments.length; i++) {
            if (i == fragmentIndex) {
                transaction.show(fragments[i]);
            } else {
                transaction.hide(fragments[i]);
            }
        }
        if (addToBackStack) {
            transaction.addToBackStack(null);
        }
        transaction.commit();
    }

這種方式也是切換Fragment的一種方式,他同樣不會影響Activity的生命週期
不過有趣的一點是──在Activity執行生命週期的時候
所有的Fragment也會同時執行生命週期XDDDD
也就是說,若你有寫一些code,想要讓使用者載入該Fragment的時候自動執行
使用這個方法的結果就是──它同時載入了所有Fragment的code,並且同時執行
由於Facebook的這個project我認為它每個主要的Fragment其實沒有什麼太特殊的功能
不然的話我想使用這個方法必須經過再三考慮

References


facebook-android-sdk/samples/Scrumptious at master · facebook/facebook-android-sdk

Fragment切換起手式

在Android版本3.0之後發行了Fragment這個新功能
從此之後就不用每次要開新的畫面就得launch新的Activity啦!
這個新功能可以做到同一個Activity內多個畫面的切換
同時又不影響整個Activity的生命週期(同場加映Fragment與Activity互相對應的生命週期)
以下將展示Fragment切換起手式

首先你會準備幾個檔案:
  1. 執行以下代碼的Activity(這邊以MainActivity.class為例) 
  2. 欲更換的Fragment(這邊以FragmentNearby.class為例) 
  3. 整個畫面的layout(這邊以main.xml為例,在欲更換Fragment的地方放置Framelayout元件,且在本例中該元件id為content)
Fragment切換起手式
// new一個新的Fragment類別物件
FragmentNearby fragmentNearby = new FragmentNearby();

// getFragmentManager, 這邊getSupportFragmentManager是為了向較舊的版本相容
FragmentManager fragmentManager = getSupportFragmentManager();

// 進行transaction
FragmentTransaction transaction = fragmentManager.beginTransaction();

// 對Fragment動手動腳,指令額外包括add, remove, hide, show...etc
transaction.replace(R.id.content, fragmentNearby);

// 確認並執行transaction!
transaction.commit();



References


Fragments | Android Developers

Sending arguments via Android, php, and external java



這是一個AMP架構下(Apache, MySQL, php)與Android連接
經由Android傳遞數值經過php再到伺服器端執行java程式後再返回Android的架構

傳遞數值的關鍵步驟大概如下面所列出:
  1. (Android)以EditText取得使用者輸入的數值,並以HTTP POST方式傳輸給php
  2. (php)取得使用者裝置傳來的數值,將數值傳入Java程式並執行
  3. (Java)執行結束後將結果返回給php
  4. (php)取得結果後以json格式封裝,傳回給使用者裝置
  5. (Android)取得結果,解碼json

1.(Android)以EditText取得使用者輸入的數值,並以HTTP POST方式傳輸給php

取得使用者input
String input = editText.getText.toString
將input封裝到params這個List裡
params.add(new BasicNameValuePair("input", input));
用HTTP POST的方式送出資料
httpPost.setEntity(new UrlEncodedFormEntity(params));

2.(php)取得使用者裝置傳來的數值,將數值傳入Java程式並執行

取得使用者input
$input= $_POST[‘input’];
執行外部程式,並代入參數
exec(“java –jar path/to/file.jar input”, $output);

3.(Java)執行結束後將結果返回給php

執行Java
public static void main(String[] args){
String input = args[0]; (中間愛幹嘛就幹嘛) System.out.print(結果); }

4.(php)取得結果後以json格式封裝,傳回給使用者裝置

取得Java傳回來的結果(它是陣列)
$result = output[0];
以json格式封裝
die(json_encode($result));

5.(Android)取得結果,解碼json

取得InputStream的內容
Is=httpEntity.getContent();
最後就可以使用BufferReader與JSONObject的方法使用結果了!

HttpClient起手式

當我們用瀏覽器(像是IE、Chrome、Firefox)在逛網站時
可以想像我們的電腦(客戶端,Client)要和網站的電腦(伺服端,Server)一直都在進行溝通
進行溝通的方式大概就是client對server發出要求(request),然後server送回應(response)給client
接著client再想辦法去解析response中所夾帶的資料~

那通常送出request有兩種方法,分別為POST和GET
兩者之間有一點點不同,但這不是這次要講的內容......
這次要說的是Android和網頁或網站傳遞資料的過程當中
所會用到的HttpClient與相關的常用程式碼寫法:

HttpClient起手式

//new一個新的HttpClient物件
DefaultHttpClient httpClient = new DefaultHttpClient();

//看是要用POST或GET方法,new一個出來,並且指定網址(url)
HttpPost httpPost = new HttpPost(url);

//將要傳遞的參數放到params裡面並設定HttpPost的Entity
httpPost.setEntity(new UrlEncodedFormEntity(params));

//new一個HttpResponse來裝httpClient執行之後的結果
HttpResponse httpResponse = httpClient.execute(httpPost);

//使用HttpEntity把內容拿出來
HttpEntity httpEntity = httpResponse.getEntity();

//(範例)內容可以應用在InputStream中
is = httpEntity.getContent();

關於params(傳遞參數,parameters)的設定,它必須使用NameValuePair格式儲存
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("username", username));
params.add(new BasicNameValuePair("password", password));

延伸問題一、HttpClient好像也很常見,和DefaultHttpClient的差別?

DefaultHttpClient是一個類別(Class),HttpClient是一個介面(Interface)
基本上藉由對HttpClient的實作已經可以應付大部分的狀況 (Default implementation of HttpClient pre-configured for most common use scenarios)
DefaultHttpClient之後也不建議使用了(Deprecated),可以使用HttpClientBuilder、CloseableHttpClient等其他方法

延伸問題二、和透過PHP表單輸入的差別?


在PHP中得透過$_POST["name"]的方式來取得表單中的數值,這邊則得要設定params

References


DefaultHttpClient (Apache HttpClient 4.3.6 API)
DefaultHttpClient is deprecated
【Android进阶学习】Http编程之HttpClient

Jena常用指令

資訊來自Jena javadoc,我將比較常用的翻譯一下
如果沒翻譯的可能就是比較不重要的,或是我現在用不到的。

Model

  • add

    Model add(Resource s, Property p, RDFNode o)
    對目前的Model新增一個statement(triple)。
    Parameters:
    s - the subject of the statement to add
    p - the predicate of the statement to add
    o - the object of the statement to add
    Returns:
    this model
  • createProperty

    Property createProperty(String nameSpace, String localName)
    Create a property with a given URI composed from a namespace part and a localname part by concatenating the strings.This method may return an existing property with the correct URI and model, or it may construct a fresh one, as it sees fit.
    Subsequent operations on the returned property may modify this model.
    Parameters:
    nameSpace - Property的命名空間。
    localName - 你幫該property取的名字。
    Returns:
    a property instance

Resouce

  • addProperty

    Resource addProperty(Property p, String o)
    對Resource(subject or object)建立property與object,成立一個三元關係(triple)。
    Parameters:
    p - 欲新增之property。
    o - property(predicate)所連接的object。
    Returns:
    This resource to allow cascading calls.
  • getProperty

    Statement getProperty(Property p)
    回傳和目前這個resource相關的property與object,若property p是property q的子類別,也會被return。
    Parameters:
    p - 欲查詢之property。
    Returns:
    回傳和目前這個resource相關的property與object,若property p是property q的子類別,也會被return。沒有statement的話會回傳null。

Jena SPARQL

SPARQL是用來查詢RDF graph的一個語法。
RDF graph就是一大堆triple的組成
在Jena中所提到的model,也就是RDF graph,statement就是triples
RDF dataset中則包含了更多的graph

SPARQL設定方面

command Line的部分 使用cd C:\位址 跳槽
若發生JENA_HOME not set
就是環境變數沒有設定
1.先echo %PATH%,看看目前有沒有紀錄到
2.沒有的話入PATH=C:\Program Files\Java\(JDK資料夾)\bin;%PATH%
3.完成!!!!!!!!!!!!!

no query string or query file
bat\sparql.bat --data=vc-db-1.rdf --query=q1.rq
把兩個檔案放到同目錄就好了

基本用法

SELECT ?欄位
WHERE
  {
      條件(必須是triple)
  }

String Matching

{FILTER regex(?g, "r", "i") }  "r"是你想查詢的字串

Testing Values

SELECT ?resource
WHERE
  {
    ?resource info:age ?age .
    FILTER (?age >= 24)          使用FILTER(條件)
  }

OPTIONAL(IF)

SELECT ?name ?age
WHERE
{
    ?person vcard:FN  ?name .
    OPTIONAL { ?person info:age ?age }
}
!bound(?age) 這是一個unbound的動作,意即不符合結果的資料也會列出

在prefix前面有小老鼠的可能是n3格式,把rdf改成n3就可以解碼,如
@prefix foaf: <http://xmlns.com/foaf/0.1/> .

UNION

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX vCard: <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?name
WHERE
{
   { [] foaf:name ?name } UNION { [] vCard:FN ?name }
}
其實sparql的語法很free,也可以修改成這樣
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX vCard: <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?name
WHERE
{
  [] ?p ?name
  FILTER ( ?p = foaf:name || ?p = vCard:FN )
}
如果把第一個例子的?name改成?name1與?name2,那結果可以更明顯的顯示
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX vCard: <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?name1 ?name2
WHERE
{
   { [] foaf:name ?name1 } UNION { [] vCard:FN ?name2 }
}

---------------------------------
| name1         | name2         |
=================================
| "Matt Jones"  |               |
| "Sarah Jones" |               |
|               | "Becky Smith" |
|               | "John Smith"  |
---------------------------------

Datasets

輸入sparql --help查看全部指令,可以看到 
Dataset
     --data=FILE            Data for the datset - triple or quad formats
     --graph=FILE           Graph for default graph of the datset(指定dataset中的預設graph)
     --namedGraph=FILE      Add a graph into the dataset as a named graph(對graph命名,並且加入dataset)
列出所有資料,語法sparql --graph ds-dft.ttl --namedgraph ds-ng-1.ttl --named graph ds-ng-2.ttl --query xxxxx.rq
PREFIX  xsd:    <http://www.w3.org/2001/XMLSchema#>
PREFIX  dc:     <http://purl.org/dc/elements/1.1/>
PREFIX  :       <.>

SELECT *
{
    { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } }
}
另外用法,注意GRAPH後面一定要先留空格
PREFIX  xsd:    <http://www.w3.org/2001/XMLSchema#>
PREFIX  dc:     <http://purl.org/dc/elements/1.1/>
PREFIX  :       <.>

SELECT *
{
    { ?s ?p ?o } UNION { GRAPH :ds-ng-1.ttl { ?s ?p ?o } } 列出和ds-ng-1.ttl聯集資料
}
也可以把指令改寫成--query xxxxx.rq 只是要把FROM與FROM NAMED加入取代--graph與--namedgraph
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX dc:  <http://purl.org/dc/elements/1.1/>
PREFIX :    <.>

SELECT *
FROM       <ds-dft.ttl>
FROM NAMED <ds-ng-1.ttl>
FROM NAMED <ds-ng-2.ttl>
{
   { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } }
}
也可以combo FILTER來使用
PREFIX  xsd:    <http://www.w3.org/2001/XMLSchema#>
PREFIX  dc:     <http://purl.org/dc/elements/1.1/>
PREFIX  :       <.>

SELECT ?date ?title
{
  ?g dc:date ?date . FILTER (?date > "2005-08-01T00:00:00Z"^^xsd:dateTime )
  GRAPH ?g
      { ?b dc:title ?title }
}

Manipulating SPARQL using ARQ

ARQ使用JAVA的方式來操作SPARQL(施工中)

References:


SPARQL Tutorial

Tutorial - Manipulating SPARQL using ARQ

Jena RDF Core API

Jena是使用Java編寫的API,可以用來做本體論的新增、查詢、修改與推論。

若還沒有建立Jena環境,可以參考如何在Eclipse中建立第一個Jena專案

以下在Jena中所提到的model、statement分別可以對應到RDF graph與triple。triple(statement)通常以名詞──動詞──名詞(主詞subject、謂詞predicate、受詞object)的方式表示,例如

Shakespear write Camelot

graph則是一大堆triple的集合。

簡單操作

1.建立Model
從ModelFactory中創造預設的Model
Model model = ModelFactory.createDefaultModel();

2.從Model中建立Resource
Resource johnSmith = model.createResource(personURI);

3.對resource增加property
//addProperty(Property p, RDFNode o)
johnSmith.addProperty(VCARD.FN, fullName);



把所有的triple列出來

1.使用Statement Iterator
StmtIterator iter = model.listStatements();

2.在while迴圈中建立Statement
Statement stmt      = iter.nextStatement(); 

3.抓出getter
Resource  subject   = stmt.getSubject();     // get the subject
Property  predicate = stmt.getPredicate();   // get the predicate
RDFNode   object    = stmt.getObject();      // get the object

4.也可以直接輸出XML格式
model.write(System.out);

Triple-annotation格式
model.write(System.out, "N-TRIPLES");

讀取rdf檔案

1.建立Model
Model model = ModelFactory.createDefaultModel();

2.使用FileManager打開檔案
 InputStream in = FileManager.get().open( inputFileName );

3.列印XML
model.read(in, null);
model.write(System.out);

P.S.:找不到vc-db-1.rdf? 放在和你放jena資料夾一樣的地方就好囉

設定前置命名空間

1.建立Model
Model model = ModelFactory.createDefaultModel();

2.設定前置
 String nsA = "http://somewhere/else#";
 String nsB = "http://nowhere/else#";

3.產生resources, properties,
 Resource root = m.createResource( nsA + "root" );//類似○,代表subject與object 
 Property P = m.createProperty( nsA + "P" );      //類似→,代表predicate
 Resource x = m.createResource( nsA + "x" );      //類似○,代表subject與object

4.將resource與property間的關係建立起來
 m.add( root, P, x );

5.查看結果
 m.write( System.out );

PS.:有沒有m.setNsPrefix的差異如下:
<j.1:P rdf:resource="http://somewhere/else#x"/> //no set
<nsA:P rdf:resource="http://somewhere/else#x"/> //set

m.read("location")
http://stackoverflow.com/questions/15782656/reading-local-owl-file-using-jena-response-filenotfoundexception

抽取URI

1.新增一個Resource, 存放等一下要retrive的resource
Resource vcard = model.getResource("http://somewhere/JohnSmith/");;

2.對Resource使用getProperty,把資料抽出來
String fullName = vcard.getProperty(VCARD.FN).getString();

PS.:這邊getString會直接輸出資料值,使用toString會輸出一整串XD

3.可以對取出的Resource增加資料
vcard.addProperty(VCARD.NICKNAME, "Smithy")

4.使用StmtIterator 來把所有的property列出來
StmtIterator iter = vcard.listProperties(VCARD.NICKNAME);

查詢Model

這邊是說如果什麼事情都用model.listStatement()來做也就太累了
1.所以使用ResIterator來代替Resource的查詢,找出有VCARD:FN屬性的資源
ResIterator iter = model.listSubjectsWithProperty(VCARD.FN);

2.進入if 迴圈 iter.hasNext print 
if (iter.hasNext()) {
    System.out.println("The database contains vcards for:");
    while (iter.hasNext()) {
        System.out.println("  " + iter.nextResource()
                                      .getProperty(VCARD.FN)
                                      .getString());
    }

有另外的方法,使用new SimpleSelector

1.再seletor中分別填入S, P, O
StmtIterator iter = model.listStatements(new SimpleSelector(null, VCARD.FN, (RDFNode) null)

操作Model

關於union, intersection與difference的操作
Model model = model1.union(model2);

容器(container)

關於容器,有三種,分別是BAG, ALT, SEQ
BAG是沒有規則隨便亂放的
ALT雖然也是沒有規則,但是基本上用來放替代品
SEQ是有規則的


Reference:

An Introduction to RDF and the Jena RDF API