11 Haziran 2016 Cumartesi

Açık kaynak olan inanılmaz servislerden Wikidata API

Wikipedia çok yaygın olarak kullanılan bir online ansiklopedi haline geldi. Bunda içerisindeki uçsuz bucaksız sayıda başlığa sahip olmasının yeri büyük. Bu platformun önemi kavranmış olmalı ki Wikidata ismiyle tüm wiki sitelerinin verilerini (wikipedia, wikinews ..) yapılandırılmış olarak depolayan ve bizlere sunan bir proje geliştirilmiş.

Wikidata API wikipedia'da bulunan herşeyi sınıflandırılmış olarak sunan istemeyeceğimiz kadar anlamlı veri elde edebileceğimiz inanılmaz bir platform. Bu API'yi oyluyo.com'da insanların oylamak istedikleri şeyleri kolayca bulabilmeleri için bir arama motoru olarak, eklenen maddelere otomatik olarak açıklama eklemek ve insanların oyladıkları şeyler ile ilgili daha fazla bilgi edinmelerini sağlayacak bilgileri çekmek için kullanıyoruz.




Örneğin, üstteki resimde açıklama ve Steffi Graf'ın fotoğrafı ve vatandaşlığına kadar tüm detayları otomatik olarak Wikidata API sayesinde çekiliyor.

Bu API'den pek faydalanılmadığını görmek beni üzdü. O yüzden API'den şöyle bir bahsetmek ve Wikidata'ya karşı ilgi uyandırmak istiyorum :) .

Wikidata'nın kendi sitesine girip herhangi birşey aratabilirsiniz ve sınıflandırılmış verileri tablolar halinde görebilirsiniz. Örneğin ben, favorim olan Inception filmini arattım:

https://www.wikidata.org/wiki/Q25188

Film ile ilgili mevcut bilgiler sitenin kullanııcı arayüzünde aşağıdaki gibi bize sunuluyor.


Tabiki maddelere programlarda kullanabileceğimiz JSON ve XML gibi formatlarda da erişebiliyoruz. Herhangi bir maddeyi sorgulatmak için wbgetentities action'ı ile sorgu yapıyoruz. Aynı yönetmen bilgisini JSON formatında almaya çalışınca da şöyle bir sonuç çıkıyor :))  https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q25188&languages=tr|en
Baya karmaşık ve anlamsız gibi görülen bu çıktı esneklik için tasarlanmış gibi. Bence bu kısım tüm wikidata yapısının bir özeti o yüzden bunu açıklayacağım gerisini İngilizce'yi iyi bilmiyor olsanız bile kavrarsınız gibime geliyor.
  
JSON çıktısını sırayla açıklamak gerekirse;

"P57" yönetmen(director) isimli özelliğin(property) ID'si. Bir maddenin özelliklerini almaya çalıştığımız zaman bize hep ID döner. Bu özelliğin Türkçe ya da İngilizce karşılığının ne olduğunu öğrenmek için ek bir sorgu göndermek gerekiyor.

"mainsnak" adlı objeler özelliğin değerini içerir. Tek yönetmen olduğu için P57 özelliğinin içerisinde sadece 1 mainsnak var ama örneğin başrolleri içeren özelliğe(cast member) bakarsanız çok fazla mainsnak içerdiğini göreceksiniz.

"datavalue" içerisindeki "numeric-id" ise örneğimizde yönetmenin id'sini verir örnekte Christopher Nolan'ın id'sini bize veriyor. API'yi kullanırken ID ile yine wbgetentities sorgusu yapıp "labels" kısmından sanatçının ismini almamız gerekecek: https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q25191&languages=tr|en

Şunu da söylemem lazım: Özellik değeri her zaman başka bir maddeye yönlendiren id olmayabiliyor. Tarih veya link gibi değerler de olabiliyor. Yazacağınız algoritmada buna önem vermeniz gerek :)  

wbgetentities action'ı id'sini bildiğimiz maddelerin özelliklerini öğrenmek için var. Tabi bunun yanı sıra wikipedia pageid'sini de bize sunuyor ve bu sayede -eğer varsa- maddenin Wikipedia'daki Türkçe açıklamasına da erişmiş oluyoruz. 

Wikidata veritabanında madde aratmak istiyorsak wbsearchentities action'ını kullanmamız gerekiyor.

Wikidata'nın en büyük problemlerinden biri Türkçe desteğinin sınırlı olması. Neyse ki genelde şehir, sanatçı gibi özel isimleri aldığım ve maddelerin Wikipedia linklerinin de sunulduğu için çok önem arz etmedi. Yine de Özellikle Türkçe Wikipedia sayfası olmayan maddeler için ihtiyaç oluyor ve onun için de Yandex Translate API'yi kullanıyorum. 

Bu yazıda sadece wbsearchentities ve wbgetentities'den bahsettim ama bu servisin onlarca farklı fonksiyonu da var. Resmi dokümantasyonundan bunlara erişebilirsiniz : https://www.wikidata.org/w/api.php

Wikidata'daki maddelerin wikipedia ansiklopedisinde bulunan açıklamalarını ve resimlerini almaya ihtiyaç duyarsanız kardeş proje olan Mediawiki'nin servisleri işinize yarayacak. https://www.mediawiki.org/wiki/API:Main_page





2 Şubat 2016 Salı

Websocket protokolü ile binary veri transferi


HTML5 ile yapılan gerçek zamanlı uygulamalar gittikçe artıyor. Websocket protokolü bu uygulamalarda server-client iletişimini sağlayan vazgeçilmez bir araç. Veri akışının çok fazla olduğu bu tarz sistemlerde gecikmeyi ve bandwith'i asgariye indirmek için gönderilen verinin optimize edilmesi gerekir.

JSON

Böyle sistemlerde insanlar tarafından kolayca okunabilmek için tasarlanmış JSON gibi formatlar verimsiz kalacaktır. Bunun nedenini bir multiplayer oyun örneği ile açıklayayım, tüm kullanıcılara saniyede yaklaşık 30 kere yollanan genel durum bilgisi aşağıdaki gibi JSON formatında olsun:
[{"id":2,"point":500,"x":55.31234535,"y":32.312332},{"id":3,"point":3000,"x":3.34261345,"y":22.52112362}]
Üstteki JSON 106 Byte boyutunda. Şimdi, boyutunu düşürmek için detayları azaltarak biraz anlamsızlaştıralım.
[[2,500,55.31234535,32.312332],[3,3000,3.34261345,22.52112362]]
Parametre isimlerini yok sayarak boyutu 63 Byte'a düşürebildik. Ama 4 tam ve 4 ondalıklı sayıyı gönderebilmek için bu kadar yer harcamak hala büyük bir sıkıntı, çünkü muhtemelen pozisyon bilgisi saniyede en az 25 defa yollanacak ve kullanıcı sayısı 2'den çok daha fazla olacak.

Bilgiyi bu şekilde string tipinde yolladığımız zaman her karakter en az 1 byte yer kaplar. Bundan dolayı örneğin id'si 2 olan oyuncunun x pozisyonu  11 byte yer kaplıyor. Bu pozisyon bilgisini float32 tipinde yollayabilseydik sadece 4 byte yer kaplayacaktı.

Binary veri transferi

Pozisyon bilgilerini float32 tipi ile tutarsak kullanıcı başına x ve y toplam 8 byte yer kaplar. ID ve puanı da int16 tipi ile tutalım onlarda toplamda 4 byte'tır. Bu hesaba göre kişi başına 12 byte ile istediğimiz bilgileri gönderebileceğiz. Yani, üstteki örnekteki iki kişinin bilgisini 24 byte'a göndermiş olacağız. Peki, bunu Javascript ile nasıl başarırız?

Javascript'te binary dizilerini oluşturmak ve görüntülemek için işe yarar sınıflar var. Örneğin binary veri dizisini temsil etmek için ArrayBuffer nesnesi kullanılır. ArrayBuffer 'a veriler Typed Array'lar aracılığıyla eklenir. Typed Array'lar binary veri oluşturmayı ve binary veriye erişmeyi sağlarlar. Gelin bahsettiğim sınıfları kullanarak örnekteki durumu gerçekleştirelim:
var players = [
  {id: 2, point: 50, x:55.31234535, y: 32.312332},
  {id: 3, point: 100, x:3.34261345, y: 22.52112362}
];
var buf = new ArrayBuffer(12*players.length);
players.forEach(function(player, index){
    var info = new Uint16Array(buf, index*12, 2);
    info.set([player.id, player.point], 0);
    var position = new Float32Array(buf, index*12+4, 2);
    position.set([player.x, player.y]);
});
ws.send(buf);

Yukarıdaki kod bloğu ArrayBuffer nesnesini oluşturduktan sonra kullanıcıların id ve puan bilgisi int16, pozisyonları ise float32 Typed Array nesneleri yardımıyla ArrayBuffer'a ekliyor ve son olarak ArrayBuffer nesnesini websocket ile client'a gönderiyor.

Server binary veriyi gönderdi... Peki client nasıl bu veriyi okuyacak ? Javascript'te bunun için DataView adlı sınıf mevcut. ArrayBuffer'a veriyi nasıl koyduysak DataView ile aynı şekilde geri alıyoruz.

var ws = new WebSocket('wss://example.com/socket');
ws.binaryType = "arraybuffer";
ws.onmessage = function(msg) {
 var players = [];
  if(msg.data instanceof ArrayBuffer) {
       var dv = new DataView(msg.data);
       for(var i = 0; i < dv.byteLength / 12; i++) {
        var player = {};
          var player.id = dv.getUint16(0);
          var player.point = dv.getUint16(2);
          var player.positionX = dv.getFloat32(4);
          var player.positionY = dv.getFloat32(8);
        players[i] = player;
       }
  }
}

Yukarıda client, server'da bulunan players dizisini, server'dan aldığı bilgiyle tekrar oluşturdu.

Böylece gönderilen binary verilerin nasıl alınacağını da görmüş olduk. Bir dahaki yazımda websocket protokolündeki ping pong sinyallerinden(heartbeat messages) bahsetmeyi düşünüyorum. O zamana kadar hoşçakalın :)