2016-05-15

Extend default ItemContainerStyle of the WPF ListBox (alternating background, highlight selected row)

Dạo này mình đụng lung tung thứ. Lâu ngày ngó vào WPF, chỉnh cái style cho ListBox. Ngồi kiếm lại cái default style của ListBox. Mình không phải dân design cũng ko có xài MS Blend. Cứ thử và sai thôi :D.

Xem thêm ở đây
https://wpf.2000things.com/2014/10/23/1186-default-itemcontainerstyle-for-a-listbox/

<Style x:Key="lbDefaultItemContainerStyle" TargetType="{x:Type ListBoxItem}">
    <Setter Property="SnapsToDevicePixels" Value="True"/>
    <Setter Property="Padding" Value="4,1"/>
    <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
    <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderBrush" Value="Transparent"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Border>
                <ControlTemplate.Triggers>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsMouseOver" Value="True"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.MouseOver.Background}"/>
                        <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.MouseOver.Border}"/>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="Selector.IsSelectionActive" Value="False"/>
                            <Condition Property="IsSelected" Value="True"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Background}"/>
                        <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Border}"/>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="Selector.IsSelectionActive" Value="True"/>
                            <Condition Property="IsSelected" Value="True"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Background}"/>
                        <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Border}"/>
                    </MultiTrigger>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="TextElement.Foreground" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Colors

<Style x:Key="FocusVisual">
    <Setter Property="Control.Template">
        <Setter.Value>
            <ControlTemplate>
                <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<SolidColorBrush x:Key="Item.MouseOver.Background" Color="#1F26A0DA"/>
<SolidColorBrush x:Key="Item.MouseOver.Border" Color="#A826A0DA"/>
<SolidColorBrush x:Key="Item.SelectedInactive.Background" Color="#3DDADADA"/>
<SolidColorBrush x:Key="Item.SelectedInactive.Border" Color="#FFDADADA"/>
<SolidColorBrush x:Key="Item.SelectedActive.Background" Color="#3D26A0DA"/>
<SolidColorBrush x:Key="Item.SelectedActive.Border" Color="#FF26A0DA"/>

Dựa vào style và mấy color này điều chỉnh thêm cho alternative row, scale font khi focus ...



<Style x:Key="FocusVisual">
 <Setter Property="Control.Template">
  <Setter.Value>
   <ControlTemplate>
    <Rectangle Margin="2" SnapsToDevicePixels="true" 
     Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" 
     StrokeThickness="1" StrokeDashArray="1 2"/>
   </ControlTemplate>
  </Setter.Value>
 </Setter>
</Style>
<SolidColorBrush x:Key="Item.MouseOver.Background" Color="#1F26A0DA"/>
<SolidColorBrush x:Key="Item.MouseOver.Border" Color="#A826A0DA"/>
<SolidColorBrush x:Key="Item.SelectedActive.Background" Color="#3D26A0DA"/>
<SolidColorBrush x:Key="Item.SelectedActive.Border" Color="#FF26A0DA"/>

<Style x:Key="AlternatingListViewItemStyle" TargetType="{x:Type ListViewItem}">
 <Setter Property="SnapsToDevicePixels" Value="True"/>
 <Setter Property="Padding" Value="4"/>
 <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
 <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
 <Setter Property="Background" Value="Transparent"/>
 <Setter Property="BorderBrush" Value="Transparent"/>
 <Setter Property="BorderThickness" Value="1"/>
 <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
 <Style.Triggers>
  <Trigger Property="ItemsControl.AlternationIndex" Value="0">
   <Setter Property="Background" Value="#FFD9F2BF"/>
  </Trigger>
  <Trigger Property="ItemsControl.AlternationIndex" Value="1">
   <Setter Property="Background" Value="White"/>
  </Trigger>
  <MultiTrigger>
   <MultiTrigger.Conditions>
    <Condition Property="IsSelected" Value="True"/>
    <Condition Property="ItemsControl.AlternationIndex" Value="0"/>
   </MultiTrigger.Conditions>
   <Setter Property="Background" Value="LightGreen"/>
  </MultiTrigger>
  <MultiTrigger>
   <MultiTrigger.Conditions>
    <Condition Property="IsSelected" Value="True"/>
    <Condition Property="ItemsControl.AlternationIndex" Value="1"/>
   </MultiTrigger.Conditions>
   <Setter Property="Background" Value="{StaticResource Item.SelectedActive.Background}"/>
   <Setter Property="BorderBrush" Value="{StaticResource Item.SelectedActive.Border}"/>
  </MultiTrigger>
  <Trigger Property="IsMouseOver" Value="True">
   <Setter Property="LayoutTransform">
    <Setter.Value>
     <ScaleTransform ScaleX="1.1" ScaleY="1.1"/>
    </Setter.Value>
   </Setter>
   <Setter Property="FontWeight" Value="SemiBold"/>                        
   <Setter Property="Background" Value="{StaticResource Item.MouseOver.Background}"/>
   <Setter Property="BorderBrush" Value="{StaticResource Item.MouseOver.Border}"/>
  </Trigger>
 </Style.Triggers>
</Style>

2016-05-11

Embedding TopoJSON on a Google Maps with D3.js

Dùng topojson của D3 chuyển về GeoJSON và thêm Data Layer như bình thường.

Format Data Layer theo như tài liệu của Google Map. Dùng function chuyển từ latLng của Google Maps sang page position để dùng cho tooltip chẳng hạn

function latLngToPoint(latLng) {

 var projection = map.getProjection();
 var bounds = map.getBounds();
 var topRight = projection
  .fromLatLngToPoint(bounds.getNorthEast());
 var bottomLeft = projection
  .fromLatLngToPoint(bounds.getSouthWest());

 var scale = Math.pow(2, map.getZoom());
 var worldPoint = projection
  .fromLatLngToPoint(latLng);

 return {
  x: (worldPoint.x - bottomLeft.x) * scale,
  y: (worldPoint.y - topRight.y) * scale
 };
}


JSFiddle

2016-05-09

Rendering TopoJSON with D3.js demo (Vietnam - Population density in 2011 by province)

Hôm nay có chút việc phải render cái map VN, thể hiện 1 số thông số như doanh thu theo vùng chẳng hạn. Nếu đơn giản thì có thể dùng Google Maps như kiểu Fusion Tables Layer hay Data Layer cũng được. Nhưng nghĩ đi nghĩ lại bê Google Maps vào thì cũng kỳ, hơn nữa mục đích cũng ko đúng. Nên mình dùng D3.js cho cái này. Kết quả cuối cùng thì ở đây, cái map nhìn thấy cũng tạm tạm.


Trước có đọc cuốn Interactive Data Visualization for the Web, rồi dùng thử D3. Nhưng từ đó giờ chỉ xài cái Sankey của D3 còn cũng không dùng nhiều. Cuốn sách này giờ có thể đọc online. Ví dụ cũng trực quan dễ hiểu.


Note lại 1 số thứ khi thực hiện

Ví dụ phần mapping thì coi chapter 12, github nó ở đây https://github.com/alignedleft/d3-book/tree/master/chapter_12.

Đầu tiên là 1 số examples cần xem:
Let’s Make a Map
Chapter 12. GeomappingInteractive Data Visualization for the Web

Thường trong sách dùng dữ liệu của Mỹ, mình sẽ dùng data VN.

Data

Đầu tiên lấy 1 data về dân số mật độ dân số tại https://www.gso.gov.vn

Mình có edit lại thêm field ISO 3166 alpha 2 (ISO 3166-2 state codes for identifying the principal subdivisions e.g., provinces or states). Có thể dùng HASC (Hierarchical administrative subdivision codes) cũng được ko có vấn  đề gì: vn-population-2011.csv.

Dữ liệu như sau:

name population area density iso region
An Giang 2151 3536.7 608 VN-44 Mekong River Delta
Bà Rịa - Vũng Tàu 1027.2 1989.5 516 VN-43 South East
Bắc Giang 1574.3 3844 410 VN-54 North East

Sau đó là dự liệu 1 số thành phố (city, township) bao gồm lat, lng, level: vn-cities.csv. Ví dụ vài dòng:

name state code type alt_type lat lng level
An Khê VN-30 VN.GL.AK Thị xã Township 14.023333 108.691389 4
Bà Rịa VN-43 VN.BV.BR Thành phố City 10.509167 107.179722 2
Bắc Giang VN-54 VN.BG.BG Thành phố City 21.293333 106.188333 2
Bắc Kạn VN-53 VN.BK.BK Thành phố City 22.133333 105.852778 3
Bạc Liêu VN-55 VN.BL.BL Thành phố City 9.268056 105.752222 2
Bắc Ninh VN-56 VN.BN.BN Thành phố City 21.18 106.066111 2
Bảo Lộc VN-35 VN.LD.BC Thành phố City 11.546667 107.793611 3
Bến Tre VN-50 VN.BR.BE Thành phố City 10.238056 106.373889 3
Biên Hòa VN-39 VN.DN.BH Thành phố City 10.941944 106.847222 1

Cuối cùng là dữ liệu map. Đầu tiên download shapefile chẳng hạn tại GADM.
Sau khi download shapefile từ GADM convert nó sang GeoJSON và TopoJSON. Mình dùng TopoJSON nên cũng ko cần convert sang GeoJSON làm gì. Mở page http://mapshaper.org/, kéo 2 file VNM_adm1.dbf với VNM_adm1.shp vào page này. OK nó sẽ render ra cái bản đồ VN. Xong mình sẽ simplify cái data này, chọn Simplify > Lấy options giả sử Visvalingam / weighted area, kéo xuống khoảng 15-20% là ổn. Để ý nếu có line intersection thì repair.

http://www.gadm.org/
GADM is a spatial database of the location of the world's administrative areas (or adminstrative boundaries) for use in GIS and similar software.

http://mapshaper.org/
A tool for topologically aware shape simplification. Reads and writes Shapefile, GeoJSON and TopoJSON formats.


Xuất ra file TopoJSON, khi đó khoảng chừng 200KB. Mình có thể mở bằng Notepad++ chẳng hạn, dùng JSON Viewer plugin format lại cho dễ nhìn. Find "objects" để đổi lại tên đang là tên của file mình kéo vào: "VNM_adm1" thành "states" chẳng hạn. Edit lại 1 số thông tin properties ví dụ:

{"name":"Trà Vinh","iso":"VN-51","hasc":"VN.TV","types":"Tỉnh","alt_types":"Province","capital":"Trà Vinh","region":8}

Dùng RegEx replace cũng khá nhanh. Trong data VNM_adm1 lưu ý có 2 entry cho tỉnh Ninh Bình và Kiên Giang (tách riêng đảo), nên chuyển qua TopoJSON mình gộp lại thành MultiPolygon luôn, còn 1 entry cho mỗi tỉnh thôi. File đã chuẩn bị xong: vn-states.json.

Mình phát hiện ra 1 điều là Github nhận ra file GeoJSON và TopoJSON nên thực hiện viewer được luôn. Công nhận sp người ta làm chiều sâu dễ sợ.

Display data

Đầu tiên để code có thể dùng JSFiddle không thì nên tạo 1 local server để dev (xài node) vì không cho load file:// không cho request resource do CORS. OK tạo 1 folder, tạo file server.js để có 1 cái localhost xài (nếu không xài JSFiddle). Mình hay xài localhost server: server.js

var http = require("http"),
    url = require("url"),
    path = require("path"),
    fs = require("fs")
    port = process.argv[2] || 8888;
 
var contentTypes = {
    '.html': 'text/html; charset=utf-8',
    '.js':   'text/javascript; charset=utf-8',
    '.json':   'application/json; charset=utf-8',
    '.css':   'text/css; charset=utf-8',
 '.png': 'image/png',
 '.gif': 'image/gif',
 '.jpeg': 'image/jpeg', 
 '.jpg': 'image/jpeg',
 '.xml': 'application/xml; charset=utf-8',
 '.kml': 'application/vnd.google-earth.kml+xml; charset=utf-8',
 '.kmz': 'application/vnd.google-earth.kmz',
};

http.createServer(function(request, response) {

  var uri = url.parse(request.url).pathname
    , filename = path.join(process.cwd(), uri);
  
  fs.exists(filename, function(exists) {
    if(!exists) {
      response.writeHead(404, {"Content-Type": "text/plain"});
      response.write("404 Not Found\n");
      response.end();
      return;
    }

    if (fs.statSync(filename).isDirectory()) { 
  filename += '/index.html';
 }

    fs.readFile(filename, "binary", function(err, file) {
      if(err) {        
        response.writeHead(500, {"Content-Type": "text/plain"});
        response.write(err + "\n");
        response.end();
        return;
      }

      response.writeHead(200, {"Content-Type": contentTypes[path.extname(filename)]});
      response.write(file, "binary");
      response.end();
    });
  });
}).listen(parseInt(port, 10));

console.log("Static file server running at\n  => http://localhost:" + port + "/\nCTRL + C to shutdown");

Nếu xài JSFiddle thì file data để trên Github xong load raw là ổn.


OK chuẩn bị xong.

Các ví dụ ban đầu thì xài GeoJSON, còn load TopoJSON thì cũng như vậy. Sau khi load json file thì chuyển ngược lại thành GeoJSON để display

// While our data can be stored more efficiently in TopoJSON,
// we must convert back to GeoJSON for display.
var features = topojson.feature(json, json.objects.states).features;

Quantile scale

Để color cho tỉnh theo density thì mình dùng scale quantile (colorful hơn là quantize). Tức là chia 63 thành 9 nhóm. Sau đó push hết density vào domain. Nếu xài quantize() thì map domain là min và max của các density. Không thì xài linear cho đơn giản.

Demostration cho scale Quantile, Quantize, Threshold Scales

Xem thêm bài d3: scales, and color. của Jerome Cukier.

// We create a quantile scale to categorize the values in 9 groups.
// The domain is static and has a minimum/maximum of population/density.
// The scale returns text values which can be used for the color CSS
// classes (q0-9, q1-9 ... q8-9)
var quantiles = d3.scale.quantile()
  .range(d3.range(9).map(function(i) {
    return 'q' + i + '-9';
  }));

...

// Set the domain of the values
quantiles.domain(features.map(function(d) {
      return d.properties.density;
    }));

g.
...
.attr('class', function(d) {
      // Use the quantiled value for the class
      return 'feature ' + quantiles(d.properties.density);
    }) // add attribute class and fill with result from quantiles

Projection

Projection function chuyển từ tọa độ geo lat, lng sang Cartesian. Mặc định projection của D3 là Albers USA (albersUsa). Projection này thực hiện chuyển các vùng như Alaska và Hawaii về hiển thị phía góc dưới bên phải của bản đồ nước Mỹ, đồng thời center map vào bản đồ nước Mỹ. Scale mặc định là 1000, nếu muốn tăng hay giảm scale thì thực hiện điều chỉnh. Mình chọn center là Sài Gòn.

var width = 960,
  height = 600;
var center = [106.34899620666437, 16.553160650957434];
var scale = 4000;
var offset = [width / 2, height / 2 - 300];

// The projection function takes a location [longitude, latitude]
// and returns a Cartesian coordinates [x,y] (in pixels).
//
// D3 has several built-in projections. Albers USA is a composite projection
// that nicely tucks Alaska and Hawaii beneath the Southwest.
//
// Albers USA (albersUsa) is actually the default projection for d3.path.geo()
// The default scale value is 1,000. Anything smaller will shrink the map;
// anything larger will expand it.
//
// Add a scale() method with 800 to our projection in order to shrink things down a bit
// var projection = d3.geo.albersUsa()
//                      .translate([w/2, h/2]).scale([800]);
projection = d3.geo.mercator()
 .translate(offset)
 .scale([scale])
 .center(center);

// We define our first path generator for translating that
// mess of GeoJSON coordinates into even messier messes of SVG path codes.
// Tell the path generator explicitly that it should reference our customized
// projection when generating all those paths
path = d3.geo.path()
 .projection(projection);

Boundary và mess

Thực hiện merge tất cả các geometries của states loại bỏ interior borders thành country boundary. Sau đó dùng topojson.mesh để có mesh giữa các tỉnh. Format bằng CSS sẽ có kết quả OK.

var boundary = g.append('g')
    .attr('class', 'boundary');

// Country boundary from merge all geometries
boundary.append('path')
    .attr('class', 'country-boundary')
    .datum(topojson.merge(json, json.objects.states.geometries))
    .attr('d', path);

...

// State mesh
boundary.append('path')
    .attr('class', 'state-boundary')
    .datum(topojson.mesh(json, json.objects.states, function(a, b) {
        return a !== b;
    })).attr('d', path);

CSS lưu ý fill là none. Trong bước chuẩn bị dữ liệu nếu line intersections quá nhiều thì mess sẽ rất tệ.


.country-boundary {
  fill: none;
  stroke: #37C3BC;
  stroke-linejoin: round;
}

.state-boundary {
  fill: none;
  stroke: #003568;
  stroke-dasharray: 5, 3;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

Data bound

Về data binding của D3, nếu parent sử dụng function data() với array sau đó enter().append() thì data của các node child là item trong array.

Nếu child node có append các node cháu thì data bound của các node cháu lúc đó cũng là item của child node. Khi đó sử dụng .text(function(d) {}) thì d là item đó.

Nếu muốn set data bound cho 1 node thì dùng function datum(). Nếu dùng datum() thì có thể forEach array và dùng datum() thay vì data binding với data().

Zooming, text ...

Phần cuối cùng zooming thì follow theo ví dụ của Mike Bostock. Text thì nên append cuối cùng vào SVG để ko bị che, đè. Điều chỉ zooming text về font-size, stroke-width + CSS.

Github: https://github.com/kierandg/d3tuts
JSFiddle: https://jsfiddle.net/kierandg/5gxacnj2/

2016-04-30

Display KML, KMZ file on Google Maps using geoXML3?


Mình xem lại cái project dùng geoXML3 hiển thị KML, KMZ. Nhưng lần này file nặng hơn nên nó chết hoài. Nói chung dùng geoXML3 để parse và hiển thị KML file lên Google Maps có một số vấn đề "ngu ngu" cần note lại:

1. File KML quá nặng ví dụ load Administrative boundary cho tỉnh/thành phố lấy từ shapefile tại
Global Administrative Areas. Chỉ cần load các tỉnh miền Nam thì Firefox cũng die ra cái dialog Debug script, Stop script.
2. Parse của geoXML3 chết do xử lý quá nặng.

Giải quyết từng vấn đề:

1a. Load từng phần (split a layer by multi documents)

Load theo từng region (miền Nam, Nam Trung Bộ + Tây Nguyên, Bắc Trung Bộ, miền Bắc), ko load toàn bộ (layer) boundary các tỉnh/thành phố. Listen dragend chẳng hạn dùng lat của map center và bounds của các vùng để load region tiếp theo.

1b. Giảm số sau dấu thập phân (reducing precision)

Giảm kích thước KML bằng các giảm precision của lat, lng còn khoảng 6 dp.

Một số QA trên StackOverflow

How accurately should I store latitude and longitude?
Accuracy versus decimal places at the equator
decimal  degrees    distance
places
-------------------------------  
0        1.0        111 km
1        0.1        11.1 km
2        0.01       1.11 km
3        0.001      111 m
4        0.0001     11.1 m
5        0.00001    1.11 m
6        0.000001   0.111 m
7        0.0000001  1.11 cm
8        0.00000001 1.11 mm
There is a difference between "number of digits used to represent a value" and the "number of meaningful digits used to represent a value".
No matter what you use to determine a location of a point on earth, there will be inaccuracies associated with your method. For GPS you are lucky to get 1 m - or a few cm if you use special correction methods.
Since the circumference of the earth is 40,000 km and 360 degrees, one degree (at the equator) corresponds to 111 km; so when you have 0.000 001 degree, you are down to 11 cm. In other words, 3 + 6 digits (in degrees) is about as accurate as you ever ought to care about.
Mình dùng XSLT để format lại file XML cái này không cần sửa 3rd library (xem file format.xslt bên dưới). Có thể chui thẳng vào code của SharpXML hay geXML để sửa.

Giả sử mình dùng C# và SharpKML, file format.xslt nhúng tromg resources Resources.TransformXslt

Lưu ý: namespace của XSLT phải khớp với namespace của file XML mới có thể transform (rất dễ lộn, một khi sai thì khó có thể nhận ra). KML generate bởi geKML (library cũ) thường là 2.1 sẽ không transform (ko match template) với namespace KML 2.2.


xmlns:gis="http://www.opengis.net/kml/2.2"

Preview:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SqlServer.Types;
using System.Data.SqlTypes;
using System.Text;
using SharpKml.Dom;
using SharpKml.Engine;
using System.IO;
using SharpKml.Base;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;

public static byte[] SerializeKml(this KmlFile kmlFile)
{
 var serializer = new Serializer();
 serializer.Serialize(kmlFile.Root);

 //var str = serializer.Xml;
 var xmlDoc = XDocument.Parse(serializer.Xml);

 var xsltTrans = new XslCompiledTransform();
 using (var sr = new StringReader(Resources.TransformXslt))
 using (var xmlReader = XmlReader.Create(sr))
 {
  xsltTrans.Load(xmlReader);
 }

 var transDoc = new XDocument();
 using (var xmlWriter = transDoc.CreateWriter())
 {
  xsltTrans.Transform(xmlDoc.CreateReader(), xmlWriter);
 }

 var xmlDeclaration = "";
 var str = xmlDeclaration + transDoc.ToString(SaveOptions.DisableFormatting);
 //var str = xmlDoc.ToString(SaveOptions.DisableFormatting);
 return Encoding.UTF8.GetBytes(str);
}


1c. Đơn giản hóa (giảm bớt số points) polygon, polyline (simplify-reduce number of points)

Dùng Line Simplification xem thêm https://bost.ocks.org/mike/simplify/ có đề cập tới thuật toán Visvalingam’s algorithm và cho rằng hiệu quả hơn Douglas-Peucker.

Simplifying multiple neighboring polygons

Xem thêm trên Stackoverflow How to simplify (reduce number of points) in KML?

Về DP algorithm Douglas-Peucker Generalization Algorithm

Một số implementation trên CodeProject
A C# Implementation of Douglas-Peucker Line Approximation Algorithm
Polyline Simplification

Nếu dùng C# với NTS TopologySuite thì trong library này có implement sẵn một số các thuật toán trong đó có DouglasPeuckerSimplifier. Chọn tolerance bao nhiêu thì OK, test thử chọn 0.001 hoặc lớn hơn .... Tốt nhất là tùy theo zoom level, nên chia thành nhiều layer ứng với zoom level với cùng chức năng.

public class DouglasPeuckerSimplifier
    Member of NetTopologySuite.Simplify
Simplifies a GeoAPI.Geometries.IGeometry using the Douglas-Peucker algorithm.

2a. Optimize của geoXML3

Optimize các option của geoXML3
processStyles: false
zoom: false không fitBounds
singleInfoWindow: true

Sửa function processPlacemarkCoords

function processPlacemarkCoords(node, tag) {
...
}

Function processPlacemarkCoords có nhiều vấn đề khi thực hiện split và parse sang lat, lng.

var path = coords.split(/\s+/g);

Nếu format trả về là chuẩn (ví dụ: không có space đi với comma, chỉ có 1 space giữa các lat,lng)
Comment đoạn bỏ space đi với dấu , (comma)
//coords = coords.replace(/,\s+/g, ',');

Biến đổi coords thành JSON string, và dùng JSON để parse.

coords = coords.replace(/\s/g, '],[');
coords = '[[' + coords + ']]';
coords = JSON.parse(coords);

Bỏ biến tạm khi parse latlng

Function cuối cùng sau khi chỉnh sửa:
Preview:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
function processPlacemarkCoords(node, tag) {
 var parent = node.getElementsByTagName(tag);
 var coordListA = [];
 for (var i = 0; i < parent.length; i++) {
  var coordNodes = parent[i].getElementsByTagName('coordinates')
  if (!coordNodes) {
   if (coordListA.length > 0) {
    break;
   } else {
    return [{ coordinates: [] }];
   }
  }

  for (var j = 0; j < coordNodes.length; j++) {
   var coords = geoXML3.nodeValue(coordNodes[j]).trim();

   // Note: have no space then comma ' ,'
   //coords = coords.replace(/,\s+/g, ',');

   /*
   var path = coords.split(/\s+/g);
   var pathLength = path.length;
   var coordList = [];
   for (var k = 0; k < pathLength; k++) {
    coords = path[k].split(',');
    if (!isNaN(coords[0]) && !isNaN(coords[1])) {
     coordList.push({
      lat: parseFloat(coords[1]),
      lng: parseFloat(coords[0]),
      alt: parseFloat(coords[2])
     });
    }
   }
   */

   // Have no double space
   //coords = coords.replace(/\s+/g, '],[');
   coords = coords.replace(/\s/g, '],[');
   coords = '[[' + coords + ']]';
   coords = JSON.parse(coords);

   var lst = {
    coordinates: []
   };

   for (var k = 0; k < coords.length; k++) {
    var c = coords[k];
    lst.coordinates.push({
     lat: parseFloat(c[1]),
     lng: parseFloat(c[0]),
     alt: parseFloat(c[2])
    });
   }

   coordListA.push(lst);
  }
 }

 return coordListA;
}


File format.xslt


Lưu ý: XSLT của .NET là version 1 nên không có function tokenize nên tạo template tokenize với tag . Precision có thể là 5 hoặc 6.
Sử dụng: chọn delimiter phù hợp, delimiter đang sử dụng cho file generated bởi SharpKML, phân cách bởi CRLF.

Có thể giảm kích thước đến 60%, xem Making KML files smaller by reducing precision


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ext="http://exslt.org/common"
    xmlns:gis="http://www.opengis.net/kml/2.2"
    version="1.0">

  <!-- This is an identity transform template - it copies all the nodes -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <!-- this template has precedence over the identity template for the `coordinates` nodes -->
  <xsl:template match="gis:coordinates">
    <xsl:copy>
      <!-- it copies the element -->
      <xsl:variable name="newline" select="'&#10;'"/>
      <xsl:variable name="coords">
        <xsl:call-template name="tokenize">
          <xsl:with-param name="string" select="." />
          <xsl:with-param name="delimiter" select="$newline" />
        </xsl:call-template>
      </xsl:variable>
      <!-- saves coordinate pairs in variable -->
      <xsl:for-each select="ext:node-set($coords)/token">
        <!-- for each coordinate pair, formats the values before and after the comma -->
        <xsl:value-of select="round(number(substring-before(.,','))*100000) div 100000"/>
        <xsl:text>,</xsl:text>
        <!-- puts the comma back between the coords -->
        <xsl:value-of select="round(number(substring-after(.,','))*100000) div 100000"/>
        <!-- puts the space back if it's not the last coord -->
        <xsl:if test="position() != last()">
          <xsl:text> </xsl:text>          
        </xsl:if>
        <!-- <xsl:value-of select="$newline"/> -->
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>
  <xsl:template name="tokenize">
    <xsl:param name="string" />
    <xsl:param name="delimiter" />
    <xsl:choose>
      <xsl:when test="contains($string, $delimiter)">
        <token>
          <xsl:value-of select="substring-before($string, $delimiter)" />
        </token>
        <xsl:call-template name="tokenize">
          <xsl:with-param name="string" select="substring-after($string, $delimiter)" />
          <xsl:with-param name="delimiter" select="$delimiter" />
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:choose>
          <xsl:when test="$string = ''">
            <xsl:text/>
          </xsl:when>
          <xsl:otherwise>
            <token>
              <xsl:value-of select="$string"/>
            </token>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

2016-04-21

ASP.NET MVC5 - Is MVCContrib dead?

MVCContrib 

Lâu ngày đụng lại ASP.NET, từ hồi MVC3 đến giờ chưa ngó qua. Coi bộ .NET mình quá lạc hậu rồi. Có 1 phần source cũ copy qua xài MVCContrib thì bị lỗi. Lý do MvcContrib.Mvc5 package xài Mvc4Futures không rõ là vì sao nhưng có lẽ do ko maintain (từ 2013) nên đụng với MVC4.

https://www.nuget.org/packages/MvcContrib.Mvc5/

Inheritance security rules violated while overriding member: ‘Microsoft.Web.Mvc.CreditCardAttribute.GetClientValidationRules(System.Web.Mvc.ModelMetadata, System.Web.Mvc.ControllerContext)’. Security accessibility of the overriding method must match the security accessibility of the method being overriden.

Xem ra thì thấy vẫn có người sử dụng http://nugetmusthaves.com/Package/MvcContrib.Mvc5

Thật ra project thường cũng chỉ một số phần nhỏ trong MVCContrib . Cách tốt nhất là phần nào cần xài thì copy từ source về project

Xem
asp.net mvc - Is MVCContrib dead? - Stack Overflow

Announcing aspnet.mvc.grid

2016-03-01

Set up Node.js, npm, Github, maven ... behind a proxy

Nếu sử dụng network sau proxy cần config mấy thứ.

# Windows


set http_proxy=[host]:[port]
set https_proxy=[host]:[port]

#Example


set http_proxy=[host]:[port]
set https_proxy=[host]:[port]
set JAVA_OPTS=-Dhttp.proxyHost=[host] -Dhttp.proxyPort=[port] -Dhttps.proxyHost=[host] -Dhttps.proxyHost=[port]



# Linux


export http_proxy=[host]:[port]
export https_proxy=[host]:[port]



# Node.js and npm


npm config set proxy [host]:[port]
npm config set https-proxy [host]:[port]

Lưu ý:

- Với Node.js và npm dùng - (dash) không phải _ (underscore).
- http hay https thì host vẫn là http ví dụ npm config set https-proxy http://x.x.x.x:80



# Maven Windows

Ví dụ với IntelliJ IDEA vào settings Maven như sau
Windows -> Preferences -> Maven -> User Settings

Edit settings.xml

/usr/local/maven/repo

Trên Windows file settings.xml tương ứng là

%USERPROFILE%/.m2/settings.xml

hoặc trong M2_HOME tùy config sử dụng Maven ntn

{M2_HOME}/conf/settings.xml

Nếu chưa có file settings.xml thì copy file settings.xml và chỉnh sửa phần cấu hình proxy <proxies>



# GitHub Windows


%USERPROFILE%/.gitconfig

[http]
    proxy = http://[host]:[port]

[https]
    proxy = http://[host]:[port]

Hoặc cài Git command for Windows https://gitforwindows.org/
Chạy git bash

git config --global http.proxy [host]:[port]
git config --global https.proxy [host]:[port]

2016-02-18

Der Langrisser (Thanh gươm quyền lực)


Der Langrisser

Một trò rất xưa mà mình chơi hết tất cả các bản dù lúc đó ko biết 1 chữ tiếng Nhật... Thật ra thì biết 1 số chữ ...canh chỉnh từ truyện Subasa nhưng cũng ko đâu vào đâu vì trong mấy trò này toàn Katakana.

ROM có thể tải để chơi giả lập:

Ở dưới là tọa độ của hidden items note lại cho vui, cũng ko cần thiết lắm. Thật ra như trò này cứ vây thằng chưởng fire và tăng máu lên level thì coi như vô đối (giống Fire Emblem vây nó và đánh bằng gươm gãy :D).

http://nicoblog.org/snes-rom/der-langrisser


Ô đầu tiên ở sát góc trái ở trên là (1, 1), cứ qua phải 1 ô thì là (2, 1) và xuống dưới 1 ô thì là (1, 2)

Scenario* | (X, Y) | Effect

----------------------------------------------------

1 | (24, 5) | MDF up
1 | (18, 25) | AT+1
----------------------------------------------------
2 | (4, 4) | Item: Speedboots
2 | (21, 30) | AT+1
----------------------------------------------------
3 | (2, 30) | Item: Speedboots
----------------------------------------------------
4 | (2, 2) | DF+1
----------------------------------------------------
5 | (17, 9) | Item: Runestone
----------------------------------------------------
6 | (11, 4) | Item: Runestone
----------------------------------------------------
7 | (13, 24) | AT+1
----------------------------------------------------
8, 37 | (30, 2) | MDF up
----------------------------------------------------
9, 43 | (6, 3) | Item: Runestone (B)
----------------------------------------------------
11, 40 | (16, 24) | Item: Runestone (C)
----------------------------------------------------
12, 53, 56 | (1, 2) | Item: Runestone
----------------------------------------------------
13, 57 | (7, 6) | MDF up
13, 57 | (19, 6) | MV up (doesn’t work)

NOTE: You must choose between 7, 6 and 19, 6.
----------------------------------------------------
14, 25, 48, 75 | (4, 10) | Item: Glaepnir
----------------------------------------------------
15, 27 | (21, 13) | MP+4
----------------------------------------------------
17, 31, 33 | (29, 9) | Item: Mirage Robe
----------------------------------------------------
18, 32, 34, 51, 63 | (7, 6) | Item: Glaepnir (NO)
18, 32, 34, 51, 63 | (22, 3) |
18, 32, 34, 51, 63 | (27, 20) | Item: Runestone (NO)
----------------------------------------------------
19, 23, 35, 46,54,67| (13, 4) | MP+2
19, 23, 35, 46,54,67| (18, 4) | AT+1

NOTE: You must choose between 13, 4 and 18, 4.

----------------------------------------------------