Zade's Weblog

程序人生

Monthly Archives: 8月 2009

文本输出的光圈效果和文本字体大小的选择

我们在前面曾经提到一种文本输出光圈效果的实现方案,这种方法简单并且实用. 但是在后面的使用中我们也遇到了一些问题.

比如我们使用4毫米大小,常规样式的的宋体输出一段比较长的文字:

image

我们看到,白色的光圈效果溢出了上面的黑色的文本输出.

我们使用同样6毫米大小同样的字体输出:

image

这个效果就上面的好不少.

 

我们再改用微软雅黑字体,得到的结果如下:

 image

 image

我们再改用仿宋体:

image

image

我们得到的结论是什么呢?

  1. 相同的算法,但是字体名称,大小,样式的不同会影响光圈的效果
  2. 目前看来,我们的算法更加的适用于较大的字体,同时字体类型比较狭小的汉字
  3. 对于单个汉字我们的这种算法更加的有效

Label和Annotation-为什么缓存很重要

在ArcGIS的术语里面,Label是动态生成的标注,Annotation是静态保存的标注.

Label有很多的好处, 可以适应矢量数据的无级比例尺的特点,但是最大的问题是实时生成,速度很慢. 更重要的是由于Label的生成总是和当前的显示设备关联在一起(例如显示器或者内存图片),所以不宜全局控制.

Annotation是静态保存的标注,所以在显示的时候会比较快,但是由于在保存的时候总是和某个特定的比例尺关联在一起,所以只能支持特定比例尺的数据.在数据缩放的时候,ArcGIS的做法是随着比例尺的改变而改变字体的大小,个人以为这种做法并不合适.原因是:从Annotation的角度来看,是为了显示标注的优化而生成的,其显示样式取决于图层本身定义的显示样式,而不是独立的显示样式.

Annotation相对于Label而言的一个很大的好处是,Annotation缓存的生成并不需要和特定设备的尺寸关联在一起,这意味着我们可以生成任意大小的缓存数据.这一点对于地图发布而言非常重要.

考虑我们要生成一副256m*256m的大图像,很显然我们是要分块缓存图片而不是直接生成一副256m*256m的栅格图片(这基本不可能).那么对于每一块我们要缓存的数据而言,可能有不少的要素跨越分块的边界;也就是说,有些要素即在这个块内,也可能位于其他的快内.为了避免注记的冲突,我们使用一个全局的冲突检测工具,其实现原理是:当注记可显示的时候,我们显示之,并同时记录其显示的外包,并且在下一次注记显示的时候检测和这些外包是否冲突.问题是当一个要素的注记只有一部分显示在当前的设备中的时候,我们记录其显示的外包已经不对了;因为显示的外包裁剪往往是封装在显示引擎里面的.

简单的说,Label的显示天生和基于全局控制的外包检测冲突消解工具是不相容的.其核心的原因是Label显示的设备的尺寸是有限的,而发布地图的大小事没有理论上的限制的.

这样,Annotation的好处就是很明显的,因为它不和显示设备关联;这也从另外一个侧面说明了缓存的重要性.

多边形标注

我们首先求得多边形的骨架线,然后把多边形标注转化为线串标注.这个思路简单,也比较容易实现,但是存在如下的问题:

1)标注的内容可能超出了多边形的边界,这是因为骨架线的标注已经不再考虑多边形的边界

2)多边形的骨架线没有严格的定义,所以也并不是很容易求得

第一个困难我们现在不用考虑,因为至今我们也没有找到比骨架线更好的做法;我们首先考虑多边形骨架线的算法.

很多论文论述了这个问题,比较有代表性是三角形求法,但是比较复杂,至少也是N*LogN的复杂度. 而且, 即便是准确的求得了多边形的骨架线, 也不能说是很好的解决了多边形标注的问题.

所以我们想通过一种简单的算法求得多边形的近似骨架线,最好是N的复杂度.我们的算法如下:

image

  1. 首先求得在x轴方向最值点(1,5);y轴方向的最值点(4,9).
  2. 比较两组最值点的差值,x_5 – x_1与y_9 – y_4的大小,取其中的较大值作为扫描点,即(1,5)
  3. 从最小的值点开始扫描,即从1开始扫描,分上下两组扫描;扫描的时候,只是增加递增点.
  4. 我们的例子生成的下组数据(1,2,3,4,5);上组数据(1,11,8,7,6,5);10,9被过滤是因为(11,10),(11,9)在x轴方面是递减点.
  5. 只是增加递增点是为了消除凹多边形的不良影响;从另外一个角度看,多边形(1,2,3,4,5,6,7,8,9,10,11,1)和多边形(1,2,3,4,5,6,7,811,1)是等价的,而后一个多边形是凸多边形
  6. 从上组点和下组点的起点到终点按特定的间隔开始扫描,扫描的交点的中点作为骨架线的控制点;首点和尾点也是骨架线的首点和尾点
  7. 在这里,特定的间隔是多少比较合适呢?如果太小,则没有效率优势,也没有必要;如果太大,则不能够反映多边形的骨架.我们在这里取多边形宽度的平均值和一个字符宽度的最大值作为特定间隔的大小.
  8. 其中,一个字符宽度保证不会太小;多边形宽度的平均值则保证不会太大.

沿线标注-实现思想

根据GeoAPI的标准,沿线标注有如下的输入参数:

  1. 起始间隔 initialGap
  2. 重复间隔 gap
  3. 终止间隔:finalGap
  4. 是否重复 isRepeated
  5. 是否沿线 isAligned
  6. 是否模拟几何体的形状 isGeneralizeLine
  7. 和标注线的垂直间隔 perpendicularOffset

其中finalGap是我们对多边形标注实现的时候(可以退化为对多边形骨架线的标注)增加的,这三个的间隔如图:

image

我们的实现思想是:

  1. 如果perpendicularOffset不为零,我们整体偏移线串的位置
  2. 定义x,y为线串起点坐标,如果initialGap不为0,则x,y为从起点沿线串正向移动initialGap距离的线上点
  3. 定义final_x,final_y为线串终止点的坐标,如果finalGap不为0,则final_x,final_y为从终点逆向移动finalGap距离的线上点
  4. 从x,y开始以字符为单位摆放注记,其实现思想参考我们前面定义的"下一个字符的位置";如果有任何的一个字符冲突,那么停止这个循环,在这种情况下,我们向前移动一个字符的距离,继续循环
  5. 循环的终止条件是摆放字符的位置超过了final_x,final_y
  6. 如果isRepeated=false,并且initialGap和finalGap都是0,我们选择在线的中点摆放注记

OGC的标准和规范

参见 OGC.

OGC制定这些规范,目的是为了使得软件产品或者服务之间的互操作方式是插件式的.

OGC规范的分类:

  1. 实现标准
    实现标准是为了描述接口和接口之间交互,提供了实现细节定义,为实现人员提供一个基本的规范;这些都通过pdf文档或者XML schema的方式提供所有的用户.
    GeoAPI以Java接口的形式定义了这些标准,这是更进一步的行为.
    实现规范是实现符合规范的GIS软件的纲领性文件.
  2. 抽象规范
    抽象规范定义了GIS的概念架构体系.
    抽象规范是定义实现标准的导引性文件
  3. 参考模型
    参考模型定义了OGC所有规范的发展框架, 这包括抽象规范, 实现标准和最佳实践的的相互关系和发展轨迹.
    参考模型对了解OGC工作模式,参与制定OGC的相关规范,想对OGC作出贡献的人很有帮助.
  4. 最佳实践
    描述了采用OGC标准的,并且向大众公开的软件实践.
  5. 讨论稿
    讨论稿的目的是为了针对某个特定的主题而在OGC工作组之间展开的讨论,这并不一定会成为标准或者规范,但是很有可能.
  6. 白皮书
    表明OGC官方立场的文献
  7. 意见征集
    征集任何对OGC标准或者规范的建设性意见

沿线标注-下一个字符的位置

假如我们对N个字符进行沿线标注,并且已经求得了第i个字符的位置,现在我们需要求得第 i+1个字符位置

输入:线串P[k,m](Pi是控制点),第i个字符的位置Pi,且Pi在线段P[i,i+1]上面,第i个字符的维度WordExtent_i,第i+1个字符的维度WordExtent_inext.

输出:第i+1个字符所在的线段P[j,j+1],以及位置Pinext.

我们先看两种情况,第一种是在x坐标轴上的延伸,如图所示:

image

两个红点是Pi和Pinext,两个矩形是两个字符的维度,虚线之间的水平距离是字符的间距,为了避免压盖,最小是(WordExtent_i.width + WordExtent_inext.width)/2.

第二个是在y坐标轴的延伸,如图所示:

image

两个红点是Pi和Pinext,两个矩形是两个字符的维度,虚线之间的垂直距离是字符的间距,为了避免压盖,最小是(WordExtent_i.height + WordExtent_inext.height )/2.

算法的第一步就要确定是在水平方向还是垂直方向上延伸,我们看下一幅图片:

image

红点是第Pi,虚线框是在四个方向延伸而不重叠的下一个字符位置的坐标点位置的x或者y的数值.如果线串的方向如图所示,那么下方和左方的虚线不必考虑,我们只是考虑上方和右方的交点V和H,我们选择哪一个作为下一个坐标点呢?很显然是H而不是V,原因是H在V的后面,我们应该节省的使用线串的资源.

我们已经从直观上确立了选择下一个字符位置的流程,剩下的就是我们从数学的角度来描述这个问题.

图像的Gamma校正

在图像处理中,我们经常听到所谓的Gamma校正,例如Gdiplus.ImageAttribute.SetGamma(…).

我们为什么需要Gamma校正,它的的效果是什么?

今天我在wikipedia上查看了这个概念,还没有中文的翻译,加上我的编程经验,理解如下:

为什么需要?

在基于阴极射线管CRT的显示系统中,图像在显示以前,信号本身会被变形. WK的解释更加的人性化,把这个过程成为天然的解码器(acts as a kind of spontaneous decoder). 那么为了适应这个解码的过程,之前就需要一个编码的过程.否则只要解码而没有编码,显示的结果就失真了.参见wiki.

也就是说,我们需要Gamma校正,是因为我们使用的CRT本身具有信号变形的特性,我们需要把这个变形校正过来.

当然如果没有这个变形特性,我们也就不需要这个过程,例如LCD.

如何矫正?

一般采用公式:image .在一般的编码过程中,gamma的数值是0.45,解码的时候是2.2.

但是也有不同的,例如mac机就是(0.55,1.8).

总而言之,解码和编码的过程都是按照幂次进行的.

效果是什么?

基本按照原样显示,下面这个说明了问题(注:Gamma=1标示不校正).

image

C++的字符集

C++的字符集,虽然很多次遇到,但是仍然不是很清楚.今天看到了一个文章,论述了这个问题,但是仍然不全面.有时间再好好整理一下这个问题.

出处:http://www.cppblog.com/chemz/archive/2007/05/30/25133.html

内容如下:

   字符集相关问题
    字符集目前有两个大的类别:本地字符集和国际字符集,其中每一类别的字符集又有多个
不同的字符编码实例。比如:本地字符集中基本上对于每一个不同的地区和国家就会形成一个
属于自己的字符集(ascii, latin-1, chs等),国际字符集中同样包括多种不同的编码方案
(utf8, utf16等)。
    那么在C/C++程序中如何完成上述字符集之间的转换工作呢?分成两种情况:
    1. 通过const char *cstr使用开发环境中的编辑器输入字符串常量"中国",如下:
            const char *cstr = "中国";
       这样一来cstr所指向的字符串内存中保存的则是本地字符编码下所形成的字符串,也
       就是说,上面的cstr中存储着chs字符编码集中的字符;
    2. 通过const wchar_t *wstr使用开发环境中的编辑器输入字符串常量"中国",如下:
            const wchar_t *wstr = L"中国";
       这样一来wstr所指向的字符串内存中保存的则是国际字符编码(在VC++下是ucs2,
       在gcc下是ucs4)下所形成的字符串,也就是说,上面的wstr中存储着utf16字符编
       码集中的字符;
    那么如何将cstr转换成为wstr呢?可以通过C语言中的标准转换函数mbstowcs来完成该工
作,此时需要注意的是如果直接使用mbstowcs进行转换会得到一个错误的结果,并不能成功
的完成转换成为国际宽字符的要求,这是为什么呢?在C/C++语言标准中定义了其运行时的
字符集环境为"C",也就是ASCII字符集的一个子集,那么mbstowcs在工作时会将cstr中所包
含的字符串看作是ASCII编码的字符,而不认为是一个包含有chs编码的字符串,所以他会将
每一个中文拆成2个ASCII编码进行转换,这样得到的结果就是会形成4个wchar_t的字符组成
的串,那么如何才能够让mbstowcs正常工作呢?在调用mbstowcs进行转换之间必须明确的告
诉mbstowcs目前cstr串中包含的是chs编码的字符串,通过setlocale( LC_ALL, "chs" )函数
调用来完成,需要注意的是这个函数会改变整个应用程序的字符集编码方式,必须要通过重
新调用setlocale( LC_ALL, "C" )函数来还原,这样就可以保证mbstowcs在转换时将cstr中
的串看作是中文串,并且转换成为2个wchar_t字符,而不是4个。

使用模式的正确方法

参见http://groups.google.com/group/pongba/browse_thread/thread/4c2d0bb7c9916800?hl=zh-CN

  1. "模式的价值就是,当我在现实世界中感受到某种特定的痛苦,我可以求助于某个已知的解决方案"
    简单的说,模式就是经验
  2. "试图使用所有的模式是不好的做法,因为你最终得到的是人工臆想出来的设计——过于深思熟虑的设计,有灵活性但却没有人需要用到。现今的软件都太复杂了。"
    这既是过度设计
  3. "模式围绕着某些中心抽象浮现出来的。"
    我们使用了很多的抽象,其中这些抽象的中心就是模式
  4. "模式语言为你提供的指导贯穿整个设计,而我们有的只是这些碎片式的,零星的工程学知识。我承认,这种做法目标不够远大,但是它仍然很重要并且很有用。"
    模式语言?或许我们还没有到那个阶段
  5. "如果你有问题,我们有相应的解决方案,但是我们没有下个步骤。我们不会给你暗示说下一步该干什么。就这种意义来说,Alexander的方法是更彻底的。"
    目标越宏大,你越容易成功—这是哪个牛人说的;但是如果目标宏大到不切实际的程度,你就无从下手了.
  6. "我认识到创建高度可重用的框架是很难的。它们会变得复杂,难以学习,而且更加难以维护。我曾经是框架的使用者,也同时是框架的创建者,从任一角度看这都很难。"
    从现在工程界的经验来看,系统级的东西容易成为框架,例如.NET Framework ,J2SE等;而行业的东西要成为框架,这些需要大量的探索和研究,例如GIS
  7. "如果你暴露了所有东西,你就不能够改动任何东西,否则你就打断了所有的使用者。这就是为什么在谈到重用的时候,组件模型,API聚焦以及设计好内部的和需要发布的东西,变得如此至关重要。"
    不要暴露实现,只是暴露接口
  8. "这里有一个重要的教训就是,API并不仅仅是一个文档化的类。而且, API 并不是自然而然产生的; 它们需要大量的投资。"
    API是领域需求向计算机实现过渡的桥梁,怎么强调都不过分
  9. "关于我们所谓受控的扩展性在Eclipse插件这个上下文中,还有另外一个重要的方面就是,扩展和扩展点。扩展点定义了你可以在哪里促成(contribute to)以及扩展某个插件。扩展点有一个名字,并且伴有一个说明,这个说明定义了当你做出贡献的时候必须遵循的接口。作为插件作者,直到考虑了你的插件可能提供的扩展点,你的工作才能算完成。"
    扩展点是框架或者平台灵活性的体现
  10. "从类到模式,最后到框架的时候,重用的层次就提升了。"
    重用性的提高意味着标准的生成和稳固
  11. "我真正喜欢的是由需求驱动的灵活性"
    不要提供用户不需要的灵活性
  12. "我们是逐步的暴露API"
    不可能一下子设计一个完美无缺的API体系
  13. "因此我对程序员的建议是,当你必须要推测的时候,一定至少要让你的一个客户参加进来,最好是多个。"
    单是程序员自身做预言并实现,几乎是最糟糕的
  14. "对于平台来说,我把长期的稳定性和它联系在一起。在平台之上搭建东西是安全的。一个平台必须保证兼容性。框架通常没有这个品质,而且我看到过许多有关框架的缺陷都与稳定性有关。如果你看看Eclipse,是的,它包括框架,工具箱,而且还提供平台API。所有这些都捆绑成插件的形式。框架通过抽象提供较高层次的默认功能。为了达到这个目的,框架需要在我们的控制之下。如果失去这种控制可能会导致有时候所谓的框架症(frameworkitis)。"
    框架,工具箱,平台,三位一体.
  15. "框架症是一种病症,它是指框架想要为你做太多事情或者它以某种你不想要而又无法改变的方式来做某件事情。"
    框架=系统默认实现+用户按需扩展.这是一种平衡,扁担失衡就会成为一种病态
  16. "使用工具箱,你创建并且调用工具箱对象,然后注册侦听者以便对事件做出响应。你自己控制这一切。框架试图得到控制权并且告诉你什么时候该干什么事情。工具箱给你积木块,但是如何控制取决于你。"
    框架是毛坯房,工具箱是砖,钢材,石灰和水泥
  17. "如果你真的想冒险使用框架,你应该试试小型的、焦点集中的并且有可能弄成可选的那些框架。"
    框架的越宏大,越难以控制;框架越小巧,越容易替换. 我们可以对比一下cairo和Qt.
  18. "给某个类增加一个依赖是很容易的。几乎是太容易了….反过来可并不那么容易了,去除一个不想要的依赖可能需要做大量的重构工作"
    我们最好不要依赖类,而是依赖抽象,依赖接口
  19. "一旦你仅仅依赖于接口,你就与实现分离开来了。这就意味着实现可以变化,而且这是一个健康的依赖关系"
    健康的依赖关系—-很好的词汇
  20. "我理解了所有接口以后,我应该就能够理解关于这个问题的语汇。"
    语汇就是我们对这个领域问题的看法和认识
  21. "这就是公共的(public)和已发布的(published)的差别所在。有些东西可以是公共的,但那并不意味着你已经把它发布出去了。"
    这怎么理解?还有些public但不是published的
  22. "拥有稳定的API是项目前进的一个关键"
  23. “从API的角度来看,定义一个可以被覆写的方法比定一个可以被调用的方法需要更严格的约束。”
    组合优于继承,因为后者需要更多的约束
  24. “模式帮助你浓缩关于设计的对话。”
    因为他们给了我们词汇表
  25. “设计从来都是关于折衷的。”
    没有最好的,只有相对较好的
  26. “找到简单的解决方案是真正的挑战所在。”
    简单但是实用, 这不容易做到
  27. “你必须得实践,在某种程度上得像个学徒。随着时间的推移你也会成为一个经验丰富的设计师。”
    纸上得来终觉浅,绝知此事要躬行.

基于制图综合的出图实现-序列化

序列化的用户很多,我们可以简单的列举一下:

  1. 保存当前的工作,在以后的某个时间继续
  2. 支持分工合作模式,部分人完成部分工作,序列化以后另一部分人继续这个工作.

我们现在讨论一下序列化的内容和格式.

序列化的内容有两大部分,一是显示的内容,另一个是数据的内容. 显示的内容和数据的内容捆绑在一起.

格式上有两种,一种是二进制格式,另一种是文本格式.二进制格式的主要优点是存储量少,保密性,私有性也好;文本格式的优点是透明, 不需要特别的软件即可打开编辑, 也符合互联网的特点.我们采用文本格式.

现在互联网大量的使用XML格式, 不过我们决定使用JSON格式.

XML需要一个庞大的解析引擎,我记得Xerces的XML解析器C++编译的静态库大约占用23M的空间,而且代码的编写也非常的复杂. 老实说, 我的觉得适应XML作为工程文件的存储格式是大材小用了,也得不偿失.

JSON是一种非常简单的格式, 它的全部优点可以简单的概括为:简单,通用, 这正是JSON让我着迷的原因. 有很多工具简单,但是不通用,例如boost::any(我在设计数据库字段的时候就不会使用它);有的通用,但是不简单,例如XML. JSON简单而通用的特点使得他非常的特别.