[Flex]用Flex构建地图应用 — 利用Google Map API制作自己的地图(2)

mikel阅读(305)

Silver 撰写  

接着上期的,这里用两个实例,给大家介绍一下Flex自定义地图制作的流程,看看是怎么从TileLayBase扩展成自定义的地图

首先是live的地图

01.package com.ityao.map
02.{
03.    import com.google.maps.CopyrightCollection;
04.    import com.google.maps.TileLayerBase;
05. 
06.    import flash.display.DisplayObject;
07.    import flash.display.Loader;
08.    import flash.events.IEventDispatcher;
09.    import flash.events.IOErrorEvent;
10.    import flash.geom.Point;
11.    import flash.net.URLRequest;
12. 
13.    public class LiveDituTileLayerBase extends TileLayerBase
14.    {
15.        private var serviceUrls:Array=["http://r0.tiles.ditu.live.com/tiles/r",
16.                                       "http://r1.tiles.ditu.live.com/tiles/r",
17.                                       "http://r2.tiles.ditu.live.com/tiles/r",
18.                                       "http://r3.tiles.ditu.live.com/tiles/r"];
19.        private var serviceUrlSuffix:String = ".png?g=29";
20. 
21.        public function LiveDituTileLayerBase()
22.        {
23.            super(new CopyrightCollection("http://cn.bing.com/ditu/"),0,19)
24.        }
25. 
26.        private function getTileUrl(p:Point,zoom:int):String{          
27. 
28.            var c:Number=Math.pow(2,zoom);
29.            var d:Number=p.x;
30.            var e:Number=p.y;
31.            var f:String="";
32.            for(var g:int=0;g<zoom;g++){
33.                c=c/2;
34.                if(e<c){
35.                    if(d<c){
36.                        f+="0"
37.                    }else{
38.                        f+="1";
39.                        d-=c
40.                    }
41.                }else{
42.                    if(d<c){
43.                        f+="2";
44.                        e-=c
45.                    }else{
46.                        f+="3";
47.                        d-=c;
48.                        e-=c
49.                    }
50.                }
51.            }
52.            var h:int=(p.x+p.y)%serviceUrls.length;
53.            return serviceUrls[h]+f+serviceUrlSuffix;
54. 
55.        }  
56. 
57.        public override function loadTile(tilePos:Point, zoom:Number):DisplayObject{
58. 
59.            var loader:Loader = new Loader();
60.            configureListeners(loader.contentLoaderInfo);
61.            var url:String = getTileUrl(tilePos,zoom);
62.            var tileUrl:URLRequest = new URLRequest(url);
63.            trace(tilePos.toString()+" z:"+zoom + " url:"+url);
64.            loader.load(tileUrl);
65.            return loader;
66. 
67.        }
68. 
69.        private function configureListeners(dispatcher:IEventDispatcher):void{
70.            dispatcher.addEventListener(IOErrorEvent.IO_ERROR,_secondaryLoad);
71.        }
72. 
73.        private function _secondaryLoad(event:IOErrorEvent):void{
74.            //image fail to load handler
75.        }
76.    }
77.}

然后是mapABC的地图

01.package com.ityao.map
02.{
03.    import com.google.maps.CopyrightCollection;
04.    import com.google.maps.TileLayerBase;
05. 
06.    import flash.display.DisplayObject;
07.    import flash.display.Loader;
08.    import flash.events.IEventDispatcher;
09.    import flash.events.IOErrorEvent;
10.    import flash.geom.Point;
11.    import flash.net.URLRequest;
12. 
13.    public class MapABCDituTileLayerBase extends TileLayerBase{
14. 
15.        public function MapABCDituTileLayerBase(){
16.            super(new CopyrightCollection("http://www.mapabc.com"),0,17)
17.        }
18. 
19.        private function getTileUrl(p:Point,zoom:int):String{
20.            var url:String = "http://emap" + ((p.x + p.y) % 4) + ".mapabc.com/mapabc/maptile?v=";
21.            url += "w2.99" ;
22.            url += "&x=" + p.x + "&y=" + p.y + "&zoom=" + (17-zoom);
23.            return url;
24.        }  
25. 
26.        public override function loadTile(tilePos:Point, zoom:Number):DisplayObject{
27. 
28.            var loader:Loader = new Loader();
29.            configureListeners(loader.contentLoaderInfo);
30.            var url:String = getTileUrl(tilePos,zoom);
31.            var tileUrl:URLRequest = new URLRequest(url);
32.            trace(tilePos.toString()+" z:"+zoom + " url:"+url);
33.            loader.load(tileUrl);
34.            return loader;
35. 
36.        }
37. 
38.        private function configureListeners(dispatcher:IEventDispatcher):void{
39.            dispatcher.addEventListener(IOErrorEvent.IO_ERROR,_secondaryLoad);
40.        }
41. 
42.        private function _secondaryLoad(event:IOErrorEvent):void{
43.            //image fail to load handler
44.        }
45.    }
46.}

在上面的例子可以看见,其实只要重载loadTile方法就可以了,是不是非常简单?

loadTile的第一个参数是图块坐标,第二个参数是zoom的图层深度
这里有篇很好的文章和实例演示告诉你这些参数是怎么来的,
http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/

使用这两个参数,我们要针对不用的图瓦(live或mapABC)去构造图瓦的链接,从而用一块块图瓦拼接出完整的地图。

图瓦的链接是怎么获得的呢?
呵呵,可以是根据不同地图的文档说明(不过通常都很少),通常我在看一个地图的时候,用firefox进行浏览,然后打开”工具”->”页面信息”,如下图所示:
bingditu

然后就发挥你的小宇宙,去猜猜那串图瓦链接怎么来的,呵呵!
我把live的猜出来了,欢迎大家补充别的链接。

[Flex]用Flex构建地图应用 — 利用Google Map API制作自己的地图(1)

mikel阅读(465)

Silver 撰写  

现 在地图应用已经成为网络应用的一个大类了,各个大的网站几乎都有自己的地图产品。最出名的莫过于国外的Google Map和国内的都市圈地图,前面Daniel Yang介绍了都市圈的Flex源代码,小弟也来一个介绍Flex构建地图的系列,希望能够让大家分享到地图开发的乐趣。

不过自己毕竟不是GIS科班出身,也没从事过GIS或者地图的真正商业开发,研究地图应用开发,纯属兴趣,所以不足之处,敬请斧正:)

构建Flex地图应用,首选Google的地图API来实现,有以下原因:

  1. 支持自定义扩展地图类型
  2. 有丰富的图形标注工具
  3. 有多种数据接口
  4. 详细的文档支持

Google map API的这些特点,可以使我们快速地开发出各式各样的地图web应用,由于本帖不是替google map打广告,就不一一介绍这些特点的详细情况了,本帖更关注于第一个特点,利用Google map api来构建属于自己的地图。

或者大家会问:构建自己的地图?可以是什么样子的呢?
小弟天马行空地替大家想象了一下,我初步想大家可以构建这样的一些应用:

  • 在专业的地图服务器里写script导出图瓦(动态或静态),然后制作自己的地图应用,最好的例子就是google地图本身
  • 利用网络的地图图瓦资源,构造地图应用,如利用API来显示e都市的三维地图,或者如后面演示的显示别的厂商如google的对手bingo的地图,mapABC的地图等
  • 通过修改现成的地图图瓦,来创造自己的地图。呵呵,站在巨人的肩膀上啊,例如你可以抓取google的图瓦,把上面的藏南地区全改回中文,然后发布出去
  • 发布大型的图画作品,例如你是卖画或者什么别的艺术品,或者你要做游戏地图,可以把照片割成图瓦,挂到网上去,让大家可以在不同分辨层次下欣赏作品
  • 协同进行艺术创作,把地图想象成大图画,大家可以在一个图画上创作,通过Map API可以进行宏观创作或者细节创作
  • … 更多更多好点子,欢迎在评论里面留言

首先贴一个swf,给大家看看用Google Map API来显示Live地图和MapABC地图的效果。


实现这个效果的步骤如下:

  1. 使用Flex的google map api,首先要去这里 下载
  2. 注册一个api使用的key,在这个页面进行注册
  3. 创建flex项目,项目目录结构如下图所示:
    mapsampleproject
    前面下载的SDK里面的map_flex_1_16.swc(下载时间不同,可能版本号不一致)需要放在项目的lib目录里面去。
  4. 创建mapsample.mxml主程序,代码如下:
    01.<?xml version="1.0" encoding="utf-8"?>
    02.<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    03.    <maps:Map xmlns:maps="com.google.maps.*" language="zh-CN" id="map"
    04.        mapevent_mapready="onMapReady(event)"
    05.        width="100%" height="100%" key="{mapKey}"/>
    06.    <mx:Script>
    07.        <![CDATA[
    08.            import com.ityao.map.MapABCDituTileLayerBase;
    09.            import com.ityao.map.GoogleDituTileLayerBase;
    10.            import com.ityao.map.LiveDituTileLayerBase;        
    11. 
    12.            import com.google.maps.overlays.TileLayerOverlay;
    13.            import com.google.maps.controls.MapTypeControl;
    14.            import com.google.maps.controls.PositionControl;
    15.            import com.google.maps.controls.ZoomControl;
    16.            import com.google.maps.LatLng;
    17.            import com.google.maps.Map;
    18.            import com.google.maps.MapEvent;
    19.            import com.google.maps.MapType;   
    20. 
    21.            //key for http://blog.ityao.com/
    22.            private static const mapKey:String = "ABQIAAAAjg2LNPeLd2SY_LMC4kTfyhREhvxbmPPYdzuafsMTRfCiNgtm-xT_QN9uPU6M7JTAKA4l_ycXr_8HOg";
    23.            private function onMapReady(event:Event):void {                  
    24. 
    25.                map.addControl(new ZoomControl());
    26.                map.addControl(new PositionControl());
    27.                map.addControl(new MapTypeControl());
    28.                map.removeMapType(MapType.PHYSICAL_MAP_TYPE);
    29.                map.removeMapType(MapType.SATELLITE_MAP_TYPE);
    30.                map.removeMapType(MapType.HYBRID_MAP_TYPE);
    31. 
    32.                //Live地图
    33.                var liveTileBase:LiveDituTileLayerBase = new LiveDituTileLayerBase();
    34.                var liveTileLayers:Array = new Array();
    35.                liveTileLayers.push(liveTileBase);
    36.                var liveMapType:MapType = new MapType(liveTileLayers,map.MERCATOR_PROJECTION,"Live地图");
    37.                liveTileBase.setMapType(liveMapType);
    38.                map.addMapType(liveMapType);
    39. 
    40.                //mapABC地图
    41.                var mapABCDituTileLayerBase:MapABCDituTileLayerBase = new MapABCDituTileLayerBase();
    42.                var mapABCDituTileLayers:Array = new Array();
    43.                mapABCDituTileLayers.push(mapABCDituTileLayerBase);
    44.                var mapABCDituMapType:MapType = new MapType(mapABCDituTileLayers,map.MERCATOR_PROJECTION,"mapABC地图");
    45.                mapABCDituTileLayerBase.setMapType(mapABCDituMapType);
    46.                map.addMapType(mapABCDituMapType);
    47.                //设置缺省的地图中心和地图类型
    48.                map.setCenter(new LatLng(23.09656,113.19219), 10);
    49.            }
    50.        ]]>
    51.    </mx:Script>
    52.</mx:Application>

    注意需要在程序中修改mapKey变量成为你前面注册的google map api key。

  5. 在主程序里面,我们创建了两种自定义的地图类型,并且添加了这两种类型进google地图中进行显示

定制自定义地图,如果地图的投影方法和google map一致的话,那只要继承TileLayerBase并重载里面的loadTile(加载图瓦)方法就可以了,下一篇将用live地图和mapABC地图为大家做介绍。

[UI]BLOG首页展示的几种方式

mikel阅读(514)

Blog的首页是一个重要的入口,所以如何能够做好入口的整理和展示非常重要。
大约在多年以前,按照日志的时间格式进行排列的类似于编年史样的风格非常流行,但是最近,摘要形式的首页展示开始变得流行起来,还有一些其他的展现形式,我们的Blogger们利用自己的聪明才智,充分的发挥了BLOG在互联网中的作用。
今天,就来介绍几种BLOG首页的展现形式。
首先,我们应该都了解首页是非常重要的,任何一个新的访客都希望从首页上获得足够多的信息,并且从易用性和导航的角度来考虑,首页也是一个站点中浏览次数最多的页面。
我们能够使用的BLOG展现形式有三种:全文形式、摘要形式、杂志形式。下面就其优缺点分别介绍一下:
1、全文形式。全文形式是指将日志文章的内容以时间为顺序全部展示出来,并不进行删减和截断。优点:
    用户可以不用离开页面而阅读全部的文章:现在已经越来越少人这么做了,但是这种方法仍保留着吸引读者的优势,唯一的缺点就是发表评论了(也许我们可以借助Ajax解决发表评论问题);
    与短文章配合的很好:如果你的日志基本上是在500字以内的短文章,那么这种全文的方式也许就非常适合;
    没有打断用户的浏览:用户不必文章看到一半再点击下一页或者全文去浏览剩余的部分;
    
2、摘要形式。摘要形式就是显示一部分文字内容,或者显示文章摘要的形式。优点:
    扫描更加容易:任何浏览者都有扫描页面的浏览习惯,而这种摘要的方式更加方便读者扫描最近的新作(想想Google Reader);
    控制表现力(Control of Design):摘要的形式更利于设计师们控制他们的设计(设计元素单元变小了,更容易组合,不必担心破坏整个布局);
    带来更多的PV:因为用户不能在首页完成全部的阅读,所以必然会被引导到另外一个全文页,这样能够增加整个站点的PV;
    避免重复的内容:全文的首页和全文页带来了内容的重复,而这是搜索引擎所不喜欢的,我们应该尽量避免;
    
3、杂志形式。这有点类似与摘要形式,但是日志的内容出现并不需要按照时间的顺序来排布,可以根据分类或者其他的指标来灵活的进行日志内容的排布。优点:
    更好的内容组织:可以将相似的内容组织在一起,有利于集中读者的注意力;
    控制表现力(Control of Design):同2;
    看起来更像典型的新闻站点;
    
任何一种BLOG的布局方式都有起特点,我们应该根据自己站点的情况和发展状况,选择合适的形式并且进行动态的调整,因为适度的变化才能更加的吸引读者。
参考资料:
1、Blog Front Page        http://www.webdesignerdepot.com/2009/09/how-to-display-your-content-on-a-blogs-front-page/

[MVC]MVC+Jquery开发B/S系统:③表单提交

mikel阅读(480)

说起表单提交,是无人不知,无人不晓哇。今天我们就谈如何用JQuery+MVC来处理表单的提交。
想达到的效果:
1、提交前的表单验证
2、表单验证
3、提交后的反馈信息
ok,先说一下工作的原理。
前台<form action='xxx.aspx' method='post'></form>,action指定了接受表单的处理页面。method这里我们只说post
如果用ajax提交表单,自然xxx.aspx便是请求的url。ajax请求后的callback便是响应表单的提交结果(错误、成功),我们提交的反馈信息用一个 浮层(lightbox)来表示。 不至于用 alert(""); 这样铛铛铛很囧。
我们引入一个JQuery的插件http://www.malsup.com/jquery/form/#api 这是一个略有名气的插件,方便易用。
关于其用法,大家可以搜索下。
那么我们需要做的就是
1、JQuery.form.js的使用 
2、lightbox的实现 
3、如何验证


//注册form的ajaxForm 此方法需要调用jQuery.ajaxwindow.js的方法
//一般form里有action,所以参数有可能只需要传一个callback,
//如果一个表单有多个提交按钮,那么则需要 提交不同的url
// 这个方法是用来注册form提交,如果有多个提交按钮时,最好默认注册一个,否则验证方法就不起到作用
$.fn.submitForm = function(args) {
    
var url, id, callback, before;
    id 
= this.attr("id");
    
if (typeof (args) == "string") {//只传一个url
        url 
= args;
        before 
= undefined;
        callback 
= undefined;
    }
    
else {
        args 
= args || new Object();
        url 
= args.url || this.attr("action");
        
if (typeof (args) == "function") {//只传一个callback
            callback 
= args;
        }
        
else {
            before 
= args.before;
            callback 
= args.callback;
        }
    }
    
//输入验证
    this.inputValidate();//这是我们需要实现的一个“输入时的验证”,
    
this.ajaxForm({ //这里调用jQuery.form.js表单注册方法。
        url: url,
        beforeSubmit: 
function(a, f, o) {//提交前的处理
            
//提交验证
            if ($("#" + id).submitValidate() == false)//这里我们需要实现的“提交时的验证”。
                
return false;
            
if (before != undefined && before() == false) {
                
return false;
            }
            o.dataType 
= "json";//指定返回的数据为json格式。
        },
        success: 
function(data) {//提交后反馈信息处理
            
if (data == "" || data == null) {
                
return false;
            }
            
var msg = new ajaxMsg(data);//这个ajaxMsg便是我们需要实现的Lightbox
            msg.show(callback);//show这个反馈的结果。
            
return false;
        }
    });
    
return this;
}


上面的方法很简单,就是form插件的使用,还有几个我们需要实现的方法。(inputValidate,submitValiedate,ajaxMsg)
既然是ajax提交,我们则需要给客户端一个反馈信息,然后用Lightbox呈现。

一:我们定义一个JsonMessage类


    public class JsonMessage
    {
        
public int result { getset; } //0错误 1正确 2提示 3警告
        public string title { getset; }//Lightbox窗口的标题
        
public string content { getset; }//message的具体内容
        
public string redirect { getset; }//弹出后自动跳转的到哪里?
        
public object callBackData { getset; }//客户端需要的数据 比如 一个新增的id或整个model


MVC返回Json(jsonmsg1),客户端的callback便可以得到这个对象的json格式。
(注 意:如果是附件的表单提交,则不能识别JsonResult格式。具体我还没有研究怎么回事,这种情况只能response一个json字符串,可以用 System.Web.Script.Serialization.JavaScriptSerializer来序列化对象,也很方便。)

二:我们写一个ajaxMsg来实现lightbox,这是我自己写的Lightbox,比较简陋,如果不喜欢,也可以用一些知名的插件。


var ajaxMsg = function(msg) {
    
if (msg != undefined) {
        
//提示框的属性
        this.result = msg.result || 0//0错误 1正确 2提示 3警告
        this.title = msg.title || ""//提示框的标题
        this.redirect = msg.redirect || null;
        
this.content = msg.content || ""//窗口的内容 通过后台json数据获得
        this.callBackData = msg.callBackData;
    }
}
//创建一个win
ajaxMsg.prototype.open = function(parentElement) {
    
//创建Mask遮罩层
    $("body").append("<div id=\"MsgMask\" class=\"mask\"></div>");
    
//设置Mask高度
    var bodyheight = document.body.offsetHeight;
    
var clientheight = document.documentElement.clientHeight;
    
if (bodyheight > clientheight)
        clientheight 
= bodyheight;
    
else
        bodyheight 
= clientheight;
    $(
"#MsgMask").height(bodyheight + "px");
    
//创建窗口
    $("body").append("<div id=\"MsgFrame\" class=\"frame\"></div>");
    
var frameId = "#MsgFrame";
    
//不同的style 不同的frame  frame由mask来控制
    switch (this.result) {
        
case 0:
            $(frameId).addClass(
"msg_error");
            
break;
        
case 1:
            $(frameId).addClass(
"msg_right");
            
break;
        
case 2:
            $(frameId).addClass(
"msg_tips");
            
break;
        
case 3:
            $(frameId).addClass(
"msg_warning");
            
break;
        
default:
            $(frameId).addClass(
"msg_default");
            
break;
    }
    
//创建内容div
    $(frameId).append("<div id=\"MsgContent\" class=\"content\">" + this.content + "</div>");
    
//设置Mask的宽高
    if (this.width > 0)
        $(frameId).width(
this.width);
    
if (this.height > 0)
        $(frameId).height(
this.height);
    
//设置窗口的定位
    var frame_width = $(frameId).width();
    
var frame_height = $(frameId).height();
    
var css_top = parseInt((document.documentElement.clientHeight  frame_height) / 2 100;
    
if (css_top <= 0)
        css_top 
= 80;
    
var css_left = (document.documentElement.clientWidth  frame_width) / 2;
    
var css_right = css_left;
    $(frameId).css(
"top", css_top + "px").css("left", css_left + "px").css("right", css_right + "px");
    hideSelects();
}
//显示方式
ajaxMsg.prototype.show = function(callBack) {
    
if (this.result == undefined) {
        
if (callBack != undefined)
            callBack(
this.callBackData);
        
return;
    }
    
if (this.result == 1) {
        
if (this.content != undefined && this.content != "") {
            
//this.open();
            //setTimeout(this.close, 1000);
            alert(this.content);
        }
        
if (callBack != undefined)
            callBack(
this.callBackData);
    }
    
else {
        
//this.open();
        //setTimeout(this.close, 2000);
        alert(this.content);
    }
}
//关闭
ajaxMsg.prototype.close = function() {
    removeMsg();
}
function removeMsg() {
    $(
"div").remove("#MsgMask");
    $(
"div").remove("#MsgFrame");
    showSelects();
}

不 知道有没有人能理解我这里的callback,我说一下他的用到的情况。 实际应用中我还有一个ajaxWin来实现弹窗,弹窗里的表单提交后一般需要关闭弹窗,然后更新一些数据(比如刷新列表)。这时就需要 submit后的callback动作。另外就是我目前还有使用到redirect这个属性。呵呵,我之前的系统需要大量的跳转处理,这些跳转也是无刷新 的,有一个数组来记录访问的地址。可以实现后退和前进。
下面是他的css

Code


三:好了,Lightbox已经实现了,也能show出各种类型的信息了。
下面还剩下表单验证。 其实表单验证大有文章可做。我这也不能一一做到。目前只做了些简单的验证。以后会实现比较复杂的错误提示效果。其实这都是 体力活,上面没要求我也懒的弄-。-

验证我采用的是给control一些自定义属性,然后再判断其值是否合法。


//输入验证
$.fn.inputValidate = function() {
    $(
"input,select,textarea"this).each(function() {
        
var isnull = $(this).attr("isnull");
        
var regexValue = $(this).attr("regex");
        
var defautValue = $(this).attr("dvalue");
        
//            //①非空注册焦点事件
        //            if (isnull == "0") {
        //                $(this).blur(function() {
        //                    if (this.value == "" || this.value == defautValue)
        //                        $(this).addClass("focus").focus();
        //                    else
        //                        $(this).removeClass("focus");
        //                });
        //            }

        
//②正则注册onchange事件
        if (regexValue != undefined) {
            
var thisValue = this.value;
            
//检查类型绑定不同事件
            if ($(this).attr("type"== "text") {
                $(
this).bind("keyup"function() {
                    
if ($(this).val() == "")
                        
return;
                    
var re = new RegExp(regexValue, "ig");
                    
var newValue = this.value;
                    
if (!re.test(newValue)) {
                        $(
this).val(thisValue).focus();
                    }
                    
else {
                        thisValue 
= newValue;
                        $(
this).val(newValue);
                    }
                });
            }
        }
        
function checkRegex(value, re) {
        }
        
//③最小长度

        
//④其他

    });
}
//提交验证
$.fn.submitValidate = function() {
    
var result = true;
    $(
"input:visible,select:visible,textarea:visible"this).each(function() {
        result 
= true;
        
var thisValue = "";
        
if ($(this).attr("type"== "radio" || $(this).attr("type"== "checkbox") {
            thisValue 
= $("input[name='" + this.name + "']:checked").val();
        }
        
else
            thisValue 
= $(this).val();
        
//判断是否违法

        
if ($(this).attr("isnull"== "0") {//① 是否必填 且不能为空或缺省值
            result = (thisValue != "" && thisValue != $(this).attr("dvalue"));
        }
        
else if (thisValue != "") {//② 是否符合格式 属性为 regex 正则
            var reValue = $(this).attr("regex");
            
if (reValue != undefined) {
                re 
= new RegExp(reValue, "ig");
                result 
= re.test(thisValue);
            }
        }
        
//        //③ 是否符合最大长度
        //        var maxLength = $(this).attr("maxLen");
        //        if (maxLength != undefined && maxLength != "-1") {
        //            if (thisValue.length > parseInt(maxLength))
        //                result = false;
        //        }
        //        //④ 是否符合最小长度

        
//返回false

        
if (result == false) {
            $(
this).addClass("focus").focus().blur(function() {
                
if (this.value != "" && this.value != $(this).attr("dvalue")) {
                    $(
this).removeClass("focus");
                }
            });
            
//alert($(this).attr("name"));
            return false;
        }
    });
    
return result;
}


这些都是比较简单的东西,拿出来献丑了。 下面是我之前写的关于绑定的,有兴趣的可以看看。

MVC+Jquery开发B/S系统:①列表绑定

[Linq]LINQ & Lambda, Part 4: Lucene.Net

mikel阅读(563)

Lucene is a jewel in the open source world – its a highly scalable, fast search engine written in Java. Its class-per-class C# cousin – Lucene.Net – is also a precious stone in the .NET universe. Over the years, Lucene.Net has gained considerable popularity and is used in a wide range of products.

C|NET, Fogbugz on Demand and Wikipedia are just a few sites that use Lucene.Net for indexing and searching their vast content. Even Microsoft has been forced to deal with a Lucene powered competitor Outlook extension that embarrasses the built-in search engine.

It's an extremely useful tool in the belt of any software engineer. Not just for it's design and implementation of modern information retrieval theories, but for it's simple API too.

From my experiences, libraries with simple concepts have simple API's. Lucene is no different because the search engine concept is very simple. A search engine is a system for finding text from a large set very quickly.

At the heart of the engine is the index (similar to a database table) with fields (like database columns) that contains documents (like database rows). To search one must write a query and give it to the engine to finding matching documents. The query language for a database is SQL and for Lucene it's a query object (you can construct complex queries by composing an object graph of query instances).

Search engines are super fast for finding text because documents are stored in an inverted index (where the terms of each field is tokenized, hashed and sorted at index time).

In contrast to database queries, search engines can calculate relevance scores when searching. This is because they use a better querying model called the vector space model instead of the classical boolean model. In the vector model, documents and queries are represented as vectors. The similarity between a query and any document can be calculated with simple vector operations. Documents with a higher similarity will appear higher in the results. Conversely, databases only know if rows meets the where criteria or not and cannot compute a relevance score – this true/false classification is how the boolean model got it's name.

Search engines are rad!! And with Lucene any developer can easily add a search engine to their application.

To satiate the rampant LINQ junkie within, I've been contributing to the LINQ to Lucene project – an open source LINQ provider framework.

My recent contribution to the project makes creating indexes and searching them even easier. Today, I'm going to demonstrate some features I've added so you too can easily Lucene-ify your application.

A common use of Lucene in applications is to complement the database with a Lucene index for searching. LINQ to Lucene offers this functionality out of the box with LINQ to SQL from data generated classes.

Let's start with the Northwind database. By following Scott Gu's instructions, you can create a DBML file with generated classes from the database schema. This demonstration only needs the Customer and order table.

linq to sql

The next step is to inform LINQ to Lucene which classes to index and how they are indexed using attributes. We do this extending the Customer and order generated classes and decorating them with attributes. If you don't know how to create partial classes, see Chris Sainty's post.

  1. [Document]  
  2. public partial class Customer {  
  3. }  
  4.   
  5. [Document]  
  6. public partial class Order {  
  7. }  

Now we've marked these two classes as documents for indexing. The next step is to specify how each field is indexed. But, there is a bit of a problem with properties in generated classes. There is no way to add attributes to generated properties without spoiling the automatically generated files.

Our solution to this is to add the MetadataType property on the DocumentAttribute which tells Lucene to look at another class for the field attributes it needs. Like so:

  1. [Document(Metadatatype= typeof(CustomerMetadata))]  
  2. public partial class Customer {  
  3. }  
  4.   
  5. [Document(Metadatatype= typeof(OrderMetadata))]  
  6. public partial class Order {  
  7. }  

Now we can create fake properties on our metadata types to specify field characteristics. The return type of the fake properties doesn't matter, only the name has to match.

  1. public class CustomerMetadata {  
  2.   
  3.     [Field(FieldIndex.Tokenized, FieldStore.Yes, IsDefault = true)]  
  4.     public object ContactName { getset; }  
  5.   
  6.     [Field(FieldIndex.Tokenized, FieldStore.Yes, IsKey= true)]  
  7.     public object CustomerID { getset; }  
  8.   
  9.     [Field(FieldIndex.Tokenized, FieldStore.Yes)]  
  10.     public object ContactTitle { getset; }  
  11.   
  12.     [Field(FieldIndex.Tokenized, FieldStore.Yes)]  
  13.     public object CompanyName { getset; }  
  14. }  
  15.   
  16. public class OrderMetadata {  
  17.   
  18.     [Field(FieldIndex.Tokenized, FieldStore.Yes, IsKey = true)]  
  19.     public object OrderID { getset; }  
  20.   
  21.     [Field(FieldIndex.Tokenized, FieldStore.Yes, IsDefault = true)]  
  22.     public object CustomerID { getset; }  
  23.   
  24.     [Field(FieldIndex.Tokenized, FieldStore.Yes)]  
  25.     public object ShipName { getset; }  
  26.   
  27.     [Field(FieldIndex.Tokenized, FieldStore.Yes)]  
  28.     public object ShipAddress { getset; }  
  29.   
  30.     [Field(FieldIndex.Tokenized, FieldStore.Yes)]  
  31.     public object ShipCity { getset; }  
  32.   
  33.     [Field(FieldIndex.Tokenized, FieldStore.Yes)]  
  34.     public object ShipCountry { getset; }  
  35. }  

Now we've told Lucene.Net which properties to index and how to index them.

  • The FieldIndex property indicates whether or not the field is tokenized (i.e. split into parts). By default, this is UnTokenized to save index space.
  • The FieldStore property tells Lucene whether or not to store the original value in the index. Again, by default, this is NO to save index space.
  • IsKey should be true for the fields primary key. Only one property can be marked as the key, so LINQ to SQL classes with composite keys should have a new uniquely identifying property.
  • IsDefault tells Lucene which field is the default field for searching.

Now we're ready to create the index.

To create the index from a LINQ to SQL Data Context, we use the DatabaseIndexSet like so:

  1. var dbi = new DatabaseIndexSet(  
  2.                                                      @"C:\index\",                  // index path                                            
  3.                                                      new NorthwindDataContext()     // data context instance                                            
  4.                                                      );   
  5. dbi.Write();  

By running this code, we'll create an index for the entire contents of the Customer and order tables. LINQ to Lucene is smart enough to collect all the relevant data from the Northwind database, convert each row to a Lucene Document and add it to the index.

Linq to lucene sample indexing

Sweet! We've got an index of the Customer and order tables.

Now we can search for Customers or orders.

Let's find all the Customers who are Marketing Managers…

  1. var mmCustomers = from c in dbi.Get()  
  2.                       where c.ContactTitle == "Marketing Manager"                         
  3.                       select c;  
  4.    
  5. Console.WriteLine("Marketing Manager Customers: Found {0}", mmCustomers.Count());  
  6.   
  7. foreach (var customer in mmCustomers) {  
  8.     Console.WriteLine(customer.ContactName);  
  9. }  

Linq to lucene sample marketing search

This very simple example demonstrates the query equality operator, but many other types of query are possible (including wildcards, prefixes, proximities). You can find more ways to query on the LINQ to Lucene homepage.

In future posts, I'll demonstrate more complex query examples and how to supply custom formatters for properties.

To see the source of LINQ to Lucene, you can get the latest release. The sample project from this post is available for download here.

NOTE: The sample project uses the Northwind database as a data source. Northwind can be downloaded and installed to your SQL Server instance from here.

[Linq]Linq to Lucene

mikel阅读(600)

    lucene是在JAVA中比较有名的开源项目,也有.NET移植版lucene.net,不过在apache的官方网站上还是一个孵化器项目,而且好像2007年就不更新了,现在codeplex上推出了LINQ To Lucene,真是一个好消息。
linq to lucene采用attribute的方式,非常简单方便。

C#代码
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using Lucene.Linq.Mapping;  
  6. using Lucene.Net.Analysis;  
  7. using Lucene.Linq;  
  8.   
  9. namespace LinqToLucene1  
  10. {  
  11.     [Document]  
  12.     public class Book : IIndexable, IHit  
  13.     {  
  14.         [Field(FieldIndex.Tokenized,FieldStore.Yes, IsDefault = true)]  
  15.         public string Title { getset; }  
  16.   
  17.         [Field(FieldIndex.Tokenized, FieldStore.Yes)]  
  18.         public string Author { getset; }  
  19.   
  20.         [Field(FieldIndex.Tokenized, FieldStore.Yes)]  
  21.         public string PubTime { getset; }  
  22.   
  23.         [Field(FieldIndex.Tokenized, FieldStore.Yes)]  
  24.         public string Publisher { getset; }  
  25.  
  26.         #region IHit Members  
  27.   
  28.         public int DocumentId { getset; }  
  29.   
  30.         public float Relevance { getset; }  
  31.          
  32.         #endregion  
  33.     }  
  34. }  

然后就可以用linq的方式查询了,真是方便啊

C#代码
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using Lucene.Net.Documents;  
  6. using Lucene.Linq.Mapping;  
  7. using Lucene.Linq;  
  8. using Lucene.Net.Analysis;  
  9.   
  10. namespace LinqToLucene1  
  11. {  
  12.     public class Program  
  13.     {  
  14.         static void Main(string[] args)  
  15.         {  
  16.             IIndex<Book> bookIndex = new Index<Book>();  
  17.             bookIndex.Add(new Book()  
  18.             {  
  19.                 Title = "谁都逃不掉的金融危机",  
  20.                 Author = "xxx",  
  21.                 Publisher = "东方出版社",  
  22.                 PubTime = "2008年12月"  
  23.             });  
  24.             bookIndex.Add(new Book()  
  25.             {  
  26.                 Title = "许我向你看(“暖伤青春代言人” 辛夷坞《致我们终将逝去的青春》完美续作)",  
  27.                 Author = "辛夷坞",  
  28.                 Publisher = "河南文艺出版社",  
  29.                 PubTime = "2008年12月"  
  30.             });  
  31.             bookIndex.Add(new Book()  
  32.             {  
  33.                 Title = "大猫儿的TT奋斗史(都市小白领的爆雷囧事录)",  
  34.                 Author = "阿巳",  
  35.                 Publisher = "国际文化出版公司",  
  36.                 PubTime = "2008年12月"  
  37.             });  
  38.             bookIndex.Add(new Book()  
  39.             {  
  40.                 Title = "佳期如梦之海上繁花(匪我思存最新作品上市)",  
  41.                 Author = "匪我思存",  
  42.                 Publisher = "新世界出版社",  
  43.                 PubTime = "2008年12月"  
  44.             });  
  45.   
  46.             var result = from book in bookIndex  
  47.                          where book.Author == "xxx"  
  48.                          select book;  
  49.   
  50.             foreach (Book book in result)  
  51.             {  
  52.                 System.Console.WriteLine(book.Title);  
  53.             }  
  54.   
  55.             System.Console.ReadLine();  
  56.         }  
  57.     }  
  58. }  

不过有个bug,如果写成from Book book in bookIndex 的话,就会报异常。

引用
一个未找到类型 “Lucene.Linq.Expressions.Query`1[[LinqToLucene1.Book, LinqToLucene1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]”上的构造函数。

[MVC]mvc实用集锦(3)

mikel阅读(534)

      这段时间非常忙,所以上一篇没有及时回复留言,请大家谅解。
      由于时间比较长了,先简单回顾一下集锦(2) 中的内容。其中主要讨论了3个问题:view与Mvc view user control中使用强类型,UpdateModel与TryUpdateModel的使用方法及排错处理,还有就是在Action中使用 FormCollection来访问请求页面标签元素的值。
      view与Mvc view user control使用强类型不仅会使页面看起来很舒服而且更具有可读性。下面的例子供大家比较一下
      使用了强类型不需要在页面中进行转换,Model就是一个Account对象

System.Web.Mvc.ViewPage<Account>
<% if (Model != null) { %>
      <div><%= Model.Name %></div>
      <div><%= Model.Age %></div>
<% } %

      没有使用强类型,这里使用了ViewData存储对象account

System.Web.Mvc.ViewPage
<% if (ViewData["account"]) != null { %>
      <% var account = ViewData["account"] as Account ;%>
      <div><%= account.Name %></div>
      <div><%= account.Age %></div>
<%}%>

      UpdateModel与TryUpdateModel有一些重载方法可以帮助我们准确的将数据装入对象,其他的就没什么了。
      下面就把今天的几个实用技巧介绍给大家,按照上一篇的顺序往下进行
8. 使用SelectList填充DropDownList
    view:

<%= Html.DropDownList("AccountType") %>

   
    controller:

List<AccountType> accountType = GetAll();
ViewData["AccountType"] = new SelectList(accountType, "ID", "Name");

    可以再后面加上默认选中的参数:

ViewData["AccountType"] = new SelectList(accountType, "ID", "Name",selectedValue);

9. 使用Html.RenderPartial调用mvc usercontrol

<% Html.RenderPartial(ViewData["PartialViewName"] as string, Model); %>

在开发中将页面分成几个usercontrol,不仅使页面逻辑更加清晰而且被定义的usercontrol可以被重用。
10. ajax请求controller返回集合
集锦1介绍了一个ajax请求controller删除记录并返回成功与否信息的实例,而这次需要返回一个数据的集合,由于ASP.NET mvc集成了JQuery框架,所以使用getJson方法实现
    controller:

public ActionResult GetAllAccount()
{
    List<Account> list = this.GetAllAccount();
    return Json(list);
}

    view:

$.getJSON('<%= Url.Action("GetAllAccount", "Account") %>',
          function(json) {
              var $container = $("#container");
              $.each(json, function() {
              $container.append('<div><a href='detail?id=' + this.Id>this.Name</a></div>');
          });
 });

controller 中的代码行 return Json(list) 将所有的信息传输到客户端,最好不要这样做,创建一个 JsonAccount 类型,其中有两个属性分别是ID,Name,只将这两个信息返回到客户端即可,当然也可以使用 KeyValuePair,这样就不用额外的定义JsonAccount 类型,使用起来更方便!

return Json(list.ConvertAll(a => new KeyValuePair<Guid, string>(a.ID, a.UserName)));

在客户端脚本也要相应的变化

$container.append('<div><a href='detail?id=' + this.Key>this.Value</a></div>');

mvc实用集锦就写到这了,希望对大家开发ASP.NET mvc程序能够有所帮助。谢谢大家的支持!!!!!

[Lucene]用lucene为数据库搜索建立增量索引

mikel阅读(508)

用 lucene 建立索引不可能每次都重新开始建立,而是按照新增加的记录,一次次的递增
建立索引的IndexWriter类,有三个参数

IndexWriter writer = new IndexWriter(path, new StandardAnalyzer(),isEmpty);


其中第三个参数是bool型的,指定它可以确定是增量索引,还是重建索引.
对于从数据库中读取的记录,譬如要为文章建立索引,我们可以记录文章的id号,然后下次再次建立索引的时候读取存下的id号,从此id后往下继续增加索引,逻辑如下.
建立增量索引,主要代码如下

public void createIndex(String path)
{
     Statement myStatement 
= null;
     String articleId
="0";
     
//读取文件,获得文章id号码,这里只存最后一篇索引的文章id
    try { 
        FileReader fr 
= new FileReader("**.txt");
        BufferedReader br 
= new BufferedReader(fr);                 
        articleId
=br.readLine();
        
if(articleId==null||articleId=="")
        articleId
="0";
        br.close();
        fr.close(); 
      } 
catch (IOException e) { 
        System.out.println(
"error343!");
        e.printStackTrace();
      }
    
try {
        
//SQL语句,根据id读取下面的内容
        String SQLText = "*****"+articleId;
        myStatement 
= conn.createStatement();
        ResultSet rs 
= myStatement.executeQuery(SQLText);
       
//写索引
        while (rs.next()) {
         Document doc 
= new Document();
         doc.add(Field.Keyword(
"**", DateAdded));
         doc.add(Field.Keyword(
"**", articleid));
         doc.add(Field.Text(
"**", URL));    
         doc.add(Field.Text(
"**", Content));
         doc.add(Field.Text(
"**", Title));    
         
try{
            writer.addDocument(doc);
          }
          
catch(IOException e){
            e.printStackTrace();
         }
           
//将我索引的最后一篇文章的id写入文件
          try { 
           FileWriter fw 
= new FileWriter("**.txt");
           PrintWriter out 
= new PrintWriter(fw);    
           out.close();
           fw.close();
           } 
catch (IOException e) { 
             e.printStackTrace();
           }
         }
            ind.Close();
            System.out.println(
"ok.end");
         }
         
catch (SQLException e){
            e.printStackTrace();
        }
        
finally {
            
//数据库关闭操作
        }        
    }

然后控制是都建立增量索引的时候根据能否都到id值来设置IndexWriter的第三个参数为true 或者是false

 boolean isEmpty = true;
 
try { 
    FileReader fr 
= new FileReader("**.txt");
    BufferedReader br 
= new BufferedReader(fr);                 
    
if(br.readLine()!= null) {
        isEmpty 
= false;
     }
     br.close();
     fr.close(); 
    } 
catch (IOException e) { 
       e.printStackTrace();
  }
            
  writer 
= new IndexWriter(Directory, new StandardAnalyzer(),isEmpty);

[SEO]SEO Google算法解析系列之HillTop算法

mikel阅读(585)

   上一篇我们介绍了潜在语义索引(LSI),今天我们将介绍超链分析的颠峰之作:HillTop算法,作为现在Google现在最核心的排名算法之一,网上不乏大量介绍她的文献。本文侧重于原始算法的分析,不考虑过多复杂因素,让您更容易理解算法本质。

      HillTop算法集PageRank,HITs、相关性算法大成于一身,由康柏系统研究中心的Krishna Bharat和多伦多大学的George A.Mihaila2001年提出并申请了专利,后授权于Google200312Google算法更新,其成为Google核心排名算法之一。

     HillTop是一种查询相关性链接分析算法,克服了的PageRank的查询无关性的缺点。简单的说HillTop算法是针对热门查询关键词来对搜索结果重新排序的一种算法。之所以针对热门关键词,这是因为HillTop算法运行效率较低的原因。算法主要分为两个过程:

一、              专家页面的寻找和评分;搜索引擎根据用户查询日志发现热门关键词后,开始针对这些热门关键词寻找专家页面,成为专家页的2个必要因素,1)必须拥有足够多而且不存在隶属关系的出链,2)至少存在一个短语包含该热门关键词的所有术语。确定专家页以后,在该页面上找出所有全部包含热门关键词中术语、或者差12两个术语的短语,将这些短语分为三个等级,分别为全部包含,差1个和差2个术语,分别对这个三等级计算等级分,等级是分对各个等级中所有短语得分的 和,而短语得分取决于这个短语在页面中位置,分数从高到低依次标题、头部和锚文本等等,然后的综合计算这个三个等级得分就得到专家分。以下举个简单的以 “汽车消费”这个热门关键词为例,“中国汽车消费网”的首页和友情链接页就是这个关键词的专家页面,因为他具有足够多而且不隶属315che.com主机域名和同Cip的出链,同时标题中的“中国汽车消费网”也包含“汽车”和“消费”这两个术语。接下来评分,先算第一等级(包含所有术语的短语)的得分,短语“中国汽车消费网”在标题中得到16分(假设),以及在锚文本中“中国汽车消费理财倾向大调查”得了1分,那么第一等级得分为17分,再算第二等级(差一个术语),第三等级(差两个术语)。这样再算三个等级得分的加权和,就是专家分,注意这三个等级权重相差非常大,在原算法的等级1到等级3的权重分别是2^32,2^16和1,因为HillTop更喜欢完全匹配。

二、              对目标页评分;一个专家页对目标页的评分等于专家本身分值×专家页可区分的短语数量取前N个指向目标页的专家页,对于多个同一隶属的专家页指向该目标页,取分值最高的专家页,然后这些专家页对目标网页的评分的和就得到,这个页面对应这个热门关键词的得分,有人称之为行业得分

我们可以看到HillTop算法通过不同等级的评分确保了评价结果对关键词的相关性,通过不同位置的评分确保了主题(行业)的相关性,通过可区分短语数防止了关键词的堆砌。

 

总结:HillTop算法存在一种博弈的思想,在链接方面同行业的网站既需要竞争更需要合作,只有被同行“认可”的网站对热门关键关键词的查询才会被排在前面。HillTop基本毁灭了小网站对热门关键词的奢望,除非你对热门关键词有超强的预期能力,但是这种流量只会持续很短的时间。当然HillTop只是排名的一个重要因素,并不是全部。

本文由中国汽车消费网(http://www.315che.com/) SEO研究中心撰写。转载请注明。

[SEO]SEO Google算法解析系列之潜在语义索引(LSI)

mikel阅读(601)

  作为一个SEOer,我们必须对搜索引擎排名算法有一定的理解,才能真正谈优化,接下来将针对Google一系列算法就我的一点理解和心得与大家共享,希望大家多多指教,相互学习。这里我们先从关键词的相关性算法开始。

   搜索引擎作弊最快的方法当属关键词堆砌,这源于信息检索中相关性算法本身的缺陷,为了对抗这种作弊方法,搜索引擎通过潜在语义索引 (Latent Semantic Indexing,LSI)算法来发现这些作弊页面,LSI算法也是信息检索领域一种古老的算法,1988年由S.T. Dumais等 人提出,主要用于自然语言理解,通过统计的方法对文档的进行语义分析,发掘同义词,相关词组等等。举个简单的例子:比方“汽车消费”这个词,通过分析大量 页面发现这个词频繁的出现在“汽车消费贷款”,“中国汽车消费网”等等这些词组中,那么机器可以认为人们的语言习惯是将“汽车消费”和“汽车消费贷款”、 “中国汽车消费网”等等联系在一起来描述一些事情。通过这样的分析发现一些由机器生成的关键词堆砌页面,因为搜索引擎认为机器生成的页面不会出现这些相关 联的词组。

  LSI算法被用于 Google的很多应用,如Adwords,Google Suggest,以及上面提到的反作弊等等。

  LSI算法提醒我们在搜索引擎优化的时候要注意页面的关键词密度,以及相关词组的使用,尽量使用比较自然的语言方式来提高页面的相关性。

原文发在:http://www.admin5.com/article/20090904/178038.shtml