2024年4月

简介

本文针对
官方例程
中的第一个例程:single-acquisition做简单的讲解,并简单分析其中调用的
arv_camera_new

arv_camera_acquisition

arv_camera_get_model_name

arv_buffer_get_image_width

arv_buffer_get_image_height
函数。

aravis版本:0.8.31
操作系统:ubuntu-20.04
gcc版本:9.4.0

源码

/* SPDX-License-Identifier:Unlicense */

/* Aravis header */

#include <arv.h>

/* Standard headers */

#include <stdlib.h>
#include <stdio.h>

/*
 * Connect to the first available camera, then acquire a single buffer.
 */

int main (int argc, char **argv)
{
	ArvCamera *camera;
	ArvBuffer *buffer;
	GError *error = NULL;

	/* Connect to the first available camera */
	camera = arv_camera_new (NULL, &error);
    //camera = arv_camera_new ("192.168.6.23", &error);

	if (ARV_IS_CAMERA (camera)) {
		printf ("Found camera '%s'\n", arv_camera_get_model_name (camera, NULL));

		/* Acquire a single buffer */
		buffer = arv_camera_acquisition (camera, 0, &error);

		if (ARV_IS_BUFFER (buffer)) {
			/* Display some informations about the retrieved buffer */
			printf ("Acquired %d×%d buffer\n",
				arv_buffer_get_image_width (buffer),
				arv_buffer_get_image_height (buffer));
			/* Destroy the buffer */
			g_clear_object (&buffer);
		}

		/* Destroy the camera instance */
		g_clear_object (&camera);
	}

	if (error != NULL) {
		/* An error happened, display the correspdonding message */
		printf ("Error: %s\n", error->message);
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

此例程较为简单,每一步的细节查看注释即可,此处不过多讲解。
运行结果:

函数说明

arv_camera_new

简介:创建一个ArvCamera对象,如果name是NULL,则连接第一个可用的相机。

ArvCamera* arv_camera_new (const char* name, GError** error)

如果是Gige相机,name的格式可以是:

  • <vendor>-<model>-<serial>
  • <vendor_alias>-<serial>
  • <vendor>-<serial>
  • <user_id>
  • <ip_address>
  • <mac_address>

例如:

  • The Imaging Source Europe GmbH-DFK 33GX265-39020369
  • The Imaging Source Europe GmbH-39020369
  • TIS-39020369
  • 192.168.0.2
  • 00:07:48:af:a2:61

Available since 0.8.0

arv_camera_acquisition

简介:获取一帧图像。
注意:①本函数会将相机设置为单帧采集模式②返回的buffer最终必须要通过g_object_unref()释放

ArvBuffer* arv_camera_acquisition(
	ArvCamera* camera,         
	guint64 timeout,     //采集超时时间,单位µs
	GError** error)

Available since: 0.8.0

arv_camera_get_model_name

简介:获取相机型号名称

const char* arv_camera_get_model_name (ArvCamera* camera, GError** error)

Available since 0.8.0

arv_buffer_get_image_width

简介:获取图片的width
注意:此函数只能在buffer payload为
ARV_BUFFER_PAYLOAD_TYPE_IMAGE

ARV_BUFFER_PAYLOAD_TYPE_EXTENDED_CHUNK_DATA

ARV_BUFFER_PAYLOAD_TYPE_MULTIPART
时使用

gint arv_buffer_get_image_width(ArvBuffer* buffer)

Available since: 0.4.0

arv_buffer_get_image_height

简介:获取图片的height
注意:此函数只能在buffer payload为
ARV_BUFFER_PAYLOAD_TYPE_IMAGE

ARV_BUFFER_PAYLOAD_TYPE_EXTENDED_CHUNK_DATA

ARV_BUFFER_PAYLOAD_TYPE_MULTIPART
时使用

gint arv_buffer_get_image_height(ArvBuffer* buffer)

Available since: 0.4.0

构建一个优秀的Prompt

在使用Stable Diffusion AI时,构建一个有效的提示(Prompt)是至关重要的第一步。这个过程涉及到创造性的尝试和对AI行为的理解。这里我会对如何构建一个好的Prompt进行一个总结。

什么是一个好的提示词

构建有效的提示是使用Stable Diffusion AI或其他AI图像生成工具的关键。一个好的提示能够精确地指导AI生成你心中所想的图像。为了帮助你创建这样的提示,我们可以探讨不同的关键词类别,并看看如何将它们组合起来生成图像。以下是一些主要的关键词类别,以及如何使用它们来构建你的提示:

  • 主题 (Subject)

这是你的图像的核心内容。它可能是一个物体、一个人、一个地方或一个事件。例如:“孤独的灯塔”、“繁忙的市场”或“宁静的森林”。

  • 媒介 (Medium)

这里指的是图像的物理或视觉表现形式。例如:“油画”、“水彩画”、“数字绘画”或“铅笔素描”。

  • 风格 (Style)

这是指图像的整体视觉风格或艺术流派。你可以指定一个特定的艺术家、艺术时期或流行的视觉风格。例如:“印象派”、“赛博朋克”、“超现实主义”或“宫崎骏动画风格”。

  • 艺术分享网站 (Art Sharing Platforms)

有时候,你可能在艺术分享网站上看到了一些你喜欢的图像,你可以在提示中提及这些网站来获取类似的风格。例如:“Behance上的数字艺术”、“DeviantArt的幻想插画”或“Pinterest上的极简主义设计”。

  • 分辨率 (Resolution)

指定你想要的图像尺寸。虽然Stable Diffusion的标准输出是512×512像素,但你可以根据需要调整。例如:“4K分辨率”、“高清壁纸”或“社交媒体缩略图”。

  • 附加细节 (Additional Details)

这些是你希望在图像中包含的特定元素或特征。例如:“穿着中世纪盔甲的骑士”、“有瀑布的热带雨林”或“未来城市的夜景”。

  • 颜色 (Color)

指定图像的主要颜色或颜色方案。例如:“暖色调”、“冷色调”、“鲜艳的色彩”或“柔和的粉色”。

  • 灯光 (Lighting)

描述你想要的光照效果。例如:“柔和的日落光线”、“戏剧性的阴影”、“明亮的阳光”或“神秘的月光”。

在构建提示时,你可以根据需要选择使用这些类别中的一个或多个。下面是一个使用这些类别构建的示例提示:

A cyberpunk-style digital artwork, displaying neon lights and flying cars in the night scene of a future city. The image adopts 4K resolution, with bright colors and strong blue and purple tones under the light

使用这个提示,你可以在Dreamshaper模型或其他适合初学者的模型中生成图像。记得,生成图像的过程是一个迭代的过程,不断尝试和调整将帮助你获得最佳结果。在后续的实践中,你还可以探索如何使用负面提示来排除你不希望出现在图像中的元素。

主题

主题
是您希望在图像中
看到
的内容。一个常见的错误是关于主题的描述不够。比如说,我们想生成一个施展魔法的女巫。一个新手可能只写了一个女巫的描述。

A witch

image-20240408230334016

你会得到一些还不错的图像,但这个提示留下了太多想象的空间。
你希望女巫的样子是怎样的?你有没有一些
关键词
来更具体地描述她?她穿着什么?她施展的是什么样的魔法?她是站着、跑步还是漂浮在空中?背景是什么样的?
Stable Diffusion 无法读取我们的想法。我们必须明确地表达我们想要的。
作为演示,比如说她是一个强大而神秘的女巫,使用闪电魔法。她穿着镶有宝石的皮革服装。她坐在一个岩石上。她戴着帽子。背景是一座城堡。

A beautiful and powerful mysterious witch, smiling, sitting on a rock, lightning magic, hat, detailed leather costume complete with gems, skirt, castle background

image-20240408230504557

现在,我们生成更
具体
的图像。服装、姿势和背景在图像间保持一致。

Medium

Medium是制作艺术作品所使用的材料。一些例子包括插画、油画、3D渲染和摄影。Medium具有强大的影响力,因为一个关键词就能极大地改变风格。
让我们加入关键词
数字艺术

A beautiful and powerful mysterious witch, smiling, sitting on a rock, lightning magic, hat, detailed leather costume complete with gems, skirt, castle background, Digital Art

image-20240408230653305

这些图像从逼真的绘画风格转变为更像是计算机图形。那么还能不能做更多的修改呢?

风格

风格
指的是图像的艺术风格。例如印象派、超现实主义、波普艺术等。
在提示中添加
超写实、幻想、黑暗艺术

A beautiful and powerful mysterious witch, smiling, sitting on a rock, lightning magic, hat, detailed leather costume complete with gems, skirt, castle background, Digital Art,Hyperrealistic, fantasy, dark art

image-20240408230836042

现在,场景变得更加阴暗和忧郁。

艺术分享网站

像Artstation和Deviant Art这样的细分图形网站汇聚了许多不同风格的图片。在提示中使用它们是将图片引向这些风格的一种方式。
让我们在提示中加入
artstation

A beautiful and powerful mysterious witch, smiling, sitting on a rock, lightning magic, hat, detailed leather costume complete with gems, skirt, castle background, Digital Art,Hyperrealistic, fantasy, dark art
artstation

这个变化并不大,但这些图片看起来确实像你在Artstation上找到的内容。

分辨率

分辨率代表着图像的清晰度和细节程度。让我们添加关键词
高度详细

清晰焦点

A beautiful and powerful mysterious witch, smiling, sitting on a rock, lightning magic, hat, detailed leather costume complete with gems, skirt, castle background, Digital Art,Hyperrealistic, fantasy, dark art
artstation,Highly detailed,clear focus 

image-20240408231220028

嗯,效果并不是很明显,也许是因为之前的图像已经相当清晰和详细了。但添加一些也无妨。

附加细节

附加细节是用来修改图像的“甜味剂”。我们将添加
科幻

反乌托邦
来为图像增添一些氛围。

A beautiful and powerful mysterious witch, smiling, sitting on a rock, lightning magic, hat, detailed leather costume complete with gems, skirt, castle background, Digital Art,Hyperrealistic, fantasy, dark art
artstation,Highly detailed,clear focus,Science fiction, dystopia

image-20240408231356070

颜色

通过添加
颜色
关键词
,您可以控制图像的整体颜色。您指定的颜色可能会以色调或对象的形式出现。
让我们用关键词
闪光金
向图像添加一些金色。

A beautiful and powerful mysterious witch, smiling, sitting on a rock, lightning magic, hat, detailed leather costume complete with gems, skirt, castle background, Digital Art,Hyperrealistic, fantasy, dark art
artstation,Highly detailed,clear focus,Science fiction, dystopia,Glitter gold

image-20240408231518031

金色在一些地方表现得非常出色!

灯光

任何摄影师都会告诉你,灯光对于创造成功的图像至关重要。灯光关键词对图像的外观有很大影响。让我们添加一些工作室灯光,使其看起来像是工作室拍摄的照片。

A beautiful and powerful mysterious witch, smiling, sitting on a rock, lightning magic, hat, detailed leather costume complete with gems, skirt, castle background, Digital Art,Hyperrealistic, fantasy, dark art
artstation,Highly detailed,clear focus,Science fiction, dystopia,Glitter gold,Studio lighting

image-20240408231644691

备注

您可能已经注意到,这些图片只需添加一些关键词就已经相当不错了。在构建提示时,并不总是越多越好。通常情况下,您并不需要太多关键词来获得好的图片。

使用负面提示

使用负面提示是另一种引导图像的好方法,但与其输入你想要的,不如输入你不想要的。它们不一定是物体,也可以是风格和不想要的属性(例如丑陋,畸形)。
对于v2模型来说,使用负面提示是必不可少的。没有它,图像看起来会远远逊色于v1的。对于v1和SDXL模型来说,它们是可选的,但是最好添加上去,因为万一有效果呢?
我将使用一个简单的通用负面提示,它不会修改风格。

Misshapen, misshapen, ugly

image-20240408231924843

构建一个优秀promot的过程

在计算机科学领域,一个好的提示是提高用户体验的关键。下面是构建一个良好提示的一般步骤:

  1. 明确提示的目的
    :首先要明确提示的目的是什么,它是为了引导用户完成某个操作,还是为了提供必要的信息。只有明确了目的,才能更好地构建提示内容。

  2. 选择合适的时机
    :提示的时机非常重要,过早或过晚的提示都会影响用户的体验。要根据用户操作的上下文,选择合适的时机进行提示。

  3. 简洁明了的内容
    :提示内容要简洁明了,避免使用过于晦涩的专业术语,让用户能够快速理解并作出相应的反应。

  4. 考虑用户反馈
    :在构建提示时,需要考虑用户的反馈,尤其是之前类似提示的反馈。通过用户反馈,及时对提示进行修改和优化。

  5. 测试和优化
    :构建完提示后,需要进行测试,观察用户对提示的反应,并根据反馈进行相应的优化和修改。

通过以上步骤,我们可以构建出一个良好的prompt,提高用户体验,促进用户完成操作。

迭代式prompt构建

构建有效的提示确实是一个迭代的过程,需要通过不断的试验和调整来完善。以下是一些策略和建议,可以帮助你在构建Stable Diffusion AI的提示时更加高效和有目的性:

1. 从简单开始

开始时,创建一个包含主题、媒介和风格的简单提示。这将为AI提供一个基本的框架来生成图像。例如:“中世纪城堡的水彩画”。

2. 批量生成和评估

每次生成至少4张图像,这样可以更好地评估提示的效果。由于AI生成的图像可能存在差异,生成多张图像可以让你有一个统计上的理解,哪些元素在提示中是有效的。

3. 逐步添加关键词

在每次迭代中,尝试添加一两个新的关键词到提示中。再次生成至少4张图像来评估新关键词的影响。这种方法可以帮助你理解每个关键词如何影响最终的图像。

4. 使用负面提示

负面提示是指导AI避免生成某些元素的强大工具。如果你发现某个模型在渲染特定对象或身体部位时存在问题,比如手部,可以在负面提示中加入这些关键词,如“无手”或“不显示手”。

5. 迭代过程中的关键词细化

随着你对模型的了解越来越深入,你可以开始将更具体的关键词添加到负面提示中。这可以帮助你更精细地控制生成的图像,避免不想要的特征或细节。

6. 记录和回顾

在整个迭代过程中,记录你的提示和生成的图像结果。这不仅有助于你追踪哪些关键词有效,哪些无效,还可以帮助你在未来的项目中更快地构建有效的提示。

通过遵循这些策略,你可以逐步完善你的提示,最终生成满足你要求的高质量图像。记住,每个AI模型都有其独特的特点和限制,因此投入时间来了解和适应你正在使用的特定模型是非常重要的。不断实践,你将能够更加熟练地使用Stable Diffusion AI来创造出令人印象深刻的视觉作品。

prompt技巧

在特定的采样步骤中,您可以通过切换到不同的关键词来修改关键词的重要性。
以下语法适用于AUTOMATIC6666661 GUI。

关键词权重


此语法适用于AUTOMATIC6666661 GUI。

您可以通过语法
(关键词: 因子)
来调整关键词的
权重

因子
是一个数值,小于1表示不太重要,大于1表示更重要。
例如,我们可以调整以下提示中关键词
dog
的权重:

(Dog:1),mist,smoke,fire,chimney,rain,damp,primitive,puddle,melting,dripping water,snow,stream,lush,ice,bridge,forest,rose,flower,Stanley Artgerm Lau,Greg Rutkowski,Thomas Kincade,Alphonse Mucha,Loish,Norman Rockwell.,

image-20240408232627367
image-20240408232814543

增加
dog
的权重倾向于生成更多的狗。减少它倾向于生成更少。对于每张图片来说并不总是正确的,但从统计意义上来说是正确的。

这种技术可以应用于主题关键词和所有类别,比如风格和光线。

()和[]语法

(
此语法适用于 AUTOMATIC6666661 GUI。
)
调整关键字强度的等效方法是使用
()

[]

  • (关键字)
    将关键字的强度增加1.1倍,与
    (关键字:1.1)
    相同。
  • [关键字]
    将关键字的强度减少0.9倍,与
    (关键字:0.9)
    相同。

你可以像在代数中一样使用多个(),效果是乘法:

  • (关键字)
    等同于
    (关键字: 1.1)
  • ((关键字))
    等同于
    (关键字: 1.21)
  • (((关键字)))
    等同于
    (关键字: 1.33)

同样,使用多个
[]
的效果是:

  • [关键字]
    等同于
    (关键字: 0.9)
  • [[关键字]]
    等同于
    (关键字: 0.81)
  • [[[关键字]]]
    等同于
    (关键字: 0.73)

AUTOMATIC6666661 提示:您可以使用 Ctrl + 上/下箭头(Windows)或 Cmd + 上/下箭头来增加/减少关键字的权重。

关键词混合


此语法适用于 AUTOMATIC6666661 GUI。

您可以混合两个关键词。正确术语是
提示调度
。语法是
[keyword1 : keyword2: factor]
factor
用来控制关键词1何时切换到关键词2。它是一个介于0和1之间的数字。例如,如果我使用提示

Oil painting portrait of [Joe biden: donald trump: 0.5]

进行30个采样步骤。
这意味着步骤1到15的提示是
Oil painting portrait of Joe biden
而步骤16到30的提示变为
Oil painting portrait of donald trump
因子确定了关键词何时改变。
改变因子的效果是以不同程度混合两位总统。

image-20240408233239023

第一个关键词决定了全局构图。早期扩散步骤确定了整体构图。后续步骤完善细节。

混合面孔

一个常见的用例是创建一个具有特定外表的新面孔,借鉴演员和女演员的特点。例如,[Emma Watson: Amber heard: 0.85]:

image-20240408233443109

当仔细选择两个名字并调整因子时,我们可以精确地得到想要的外表。
另外,你可以使用
多个名人的名字
和关键词权重来调整面部特征。例如:
(Emma Watson:0.5), (Tara Reid:0.9), (Ana de Armas:1.2)

image-20240408233558748

从提示到提示

通过关键词混合,你可以实现类似于提示到提示的效果,生成一对高度相似的图像并进行编辑。以下两幅图像是使用相同的提示生成的,除了一个地方不同,那就是使用了apple 和fire混合。种子和步数保持不变。

image-20240408233958055

拿着一个[apple: fire: 0.3]

image-20240408234105072

这背后的理论是图像的整体构图是由早期的扩散过程设定的。一旦扩散被困在一个小空间里,交换任何关键词对整体图像的影响不会很大。它只会改变一个小部分。

保持一致的面孔

使用多个名人的名字是混合两个或更多面孔的简单方法。混合在不同的图片上都是一致的。当你使用多个名字时,Stable diffusion会理解为生成一个人,但具有这些面部特征。

下面的短语使用多个名字来混合三个具有不同权重的面孔。

(Emma Watson:0.5), (Tara Reid:0.9), (Ana de Armas:1.2)

我们试下这种方式的效果,提示是:

(Emma Watson:0.5), (Tara Reid:0.9), (Ana de Armas:1.2),Photo of a young lady, prominent hair, sitting outside a restaurant, wearing a dress, edge lighting, studio lighting, looking into the lens, DSLR camera, Ultra High quality, Clear focus, depth of field, film grain, Fujifilm XT3, Crystal clear, 8K Ultra HD, Highly detailed shiny eyes, Highly detailed skin, skin pores

以下是具有相同提示的图片:

image-20240408234310867

看到这张脸在不同的图片上重复出现了吧!
使用多个名人的名字和关键词权重来精心调整你想要的面部特征。你还可以在负面提示中使用名人的名字来避免你不想要的面部特征。

提示可以有多长?

根据您使用的Stable Diffusion服务不同,提示中可以使用的关键词数量可能会有限制。在基本的Stable Diffusion v1模型中,这个限制是75个token。
请注意,token与单词不同。
例如,
dream
是一个token,
beach
是另一个token。但dreambeach是两个token,因为这个模型不认识这个单词,所以模型会将这个单词分解成
dream

beach
,它认识这两个单词。

AUTOMATIC6666661中的提示限制

AUTOMATIC6666661没有token限制。如果一个提示包含超过75个token,也就是CLIP分词器的限制,它将开始一个新的另外75个token的块,所以新的“限制”变成了150。这个过程可以无限延续,或者直到您的计算机内存用尽。
每个75个token的块都是独立处理的,处理后的表示会在输入到Stable diffusion的U-Net之前进行连接。
在AUTOMATIC6666661中,您可以通过查看提示输入框右上角的小框来检查令牌的数量。

image-20240408234607888

开始一个新的提示块

如果你想在达到75个token之前开始一个新的提示块怎么办?有时候你想这样做是因为提示块开头的标记更有效,而且你可能想将相关关键词分组在一个提示块中。
你可以使用关键词
BREAK
来开始一个提示块。下面的提示使用了两个提示块来指定帽子是白色的,裙子是蓝色的。

A picture of a woman in a white hat 
BREAK 
Blue skirt

image-20240408234739884

如果没有使用BREAK,Stable Diffusion更有可能混淆帽子和裙子的颜色。

image-20240408234909833

名人姓名的关联

每个关键词都会有一些意想不到的关联。这在名人姓名中尤其如此。一些演员喜欢摆特定的姿势或穿特定的服装拍照,这也会出现在训练数据中。如果你想想,模型训练本质上就是通过关联学习。如果 Taylor Swift(在训练数据中)总是交叉双腿,模型会认为交叉双腿也是 Taylor Swift。

当你在提示中使用 Taylor Swift 时,你可能本意是使用她的脸。但是主题的姿势和服装也会产生影响。可以通过仅使用她的名字作为提示来研究这种影响。

姿势和服装是全局构图。如果你想要她的脸但不要她的姿势,你可以使用关键词混合来在后续采样步骤中替换她。

点我查看更多精彩内容:www.flydean.com

概述

Plugin,意为插件,是mybatis为开发者提供的,对方法进行自定义编程的手段。其中用到了动态代理、反射方法,通过指定需要增强的对象与方法,进行程序编写。

核心类

主要涉及几个核心类:
Interceptor

Plugin

Intercepts

该增强功能的大致执行顺序为:

  1. 项目启动时,查询实现了
    Interceptor
    接口并且注册为Bean(或在xml文件中指定)的类,放入
    SqlSessionFactoryBean
    的Interceptor[]参数中,再由
    SqlSessionFactoryBean
    创建
    SqlSessionFactory
    的时候,将其放入
    Configuration
    参数中,留作后续调用

    // **注意interceptorsProvider,此为SpringBoot的configuration,会自动查询注册为Bean的Interceptor**
    public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
          ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
          ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
          ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
          ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) {
        this.properties = properties;
        this.interceptors = interceptorsProvider.getIfAvailable();
        this.typeHandlers = typeHandlersProvider.getIfAvailable();
        this.languageDrivers = languageDriversProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.databaseIdProvider = databaseIdProvider.getIfAvailable();
        this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
        this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable();
     }
     
     
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
      SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
      factory.setDataSource(dataSource);
      .....
      // **注意此处,将interceptors放入factory中**
      if (!ObjectUtils.isEmpty(this.interceptors)) {
        factory.setPlugins(this.interceptors);
      }
    	.....
    }
    
    public class SqlSessionFactoryBean
    		protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    		....... 
    	    if (!isEmpty(this.typeAliases)) {
    	      Stream.of(this.typeAliases).forEach(typeAlias -> {
    	        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
    	        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
    	      });
    	    }
    	
    	    if (!isEmpty(this.plugins)) {
    	      Stream.of(this.plugins).forEach(plugin -> {
    	        targetConfiguration.addInterceptor(plugin);
    	        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
    	      });
    	    }
    			.......
    	
    	    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
    	  }
    }
    
  2. Configuration类在初始化
    ParameterHandler

    ResultSetHandler

    StatementHandler

    Executor
    四个类时,会对它们进行一次封装,封装内容即为用Interceptors注册插件功能,达到增强效果

    public class Configuration {
    
    	public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
      }
    
      public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
          ResultHandler resultHandler, BoundSql boundSql) {
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
      }
    
      public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }
      
      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
          executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
          executor = new ReuseExecutor(this, transaction);
        } else {
          executor = new SimpleExecutor(this, transaction);
        }
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }
    
    }
    
  3. Configuration
    执行的pluginAll方法,内部是通过遍历Interceptor数组的plugin方法实现的。该方法入参和出参都是Object类型,所以可以认为它能为所有类型对象都进行增强封装。
    Interceptor
    内部调用了
    Plugin
    的wrap方法,对Object对象进行了封装。

    public class InterceptorChain {
    
      private final List<Interceptor> interceptors = new ArrayList<>();
    
      public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
          target = interceptor.plugin(target);
        }
        return target;
      }
    
      public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
      }
    
      public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
      }
    
    }
    
    public interface Interceptor {
    
      Object intercept(Invocation invocation) throws Throwable;
    
      default Object plugin(Object target) {
        return Plugin.wrap(target, this);
      }
    
      default void setProperties(Properties properties) {
        // NOP
      }
    
    }
    
  4. Plugin
    方法实现了
    InvocationHandler
    动态代理类,并且wrap方法本身便是创建动态代理类。故Plugin类的职责有两项:


    1. 创建动态代理类,指定需要被代理(增强)的对象。此处为
      Executor

      Handler
      等。
    2. 指定被动态代理的对象,需要执行何种程序。重点关注
      invoke
      方法。

    public class Plugin implements InvocationHandler {
    
      private final Object target;
      private final Interceptor interceptor;
      private final Map<Class<?>, Set<Method>> signatureMap;
    
      private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
      }
    
    	// **创建动态代理对象**
      public static Object wrap(Object target, Interceptor interceptor) {
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
          return Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              new Plugin(target, interceptor, signatureMap));
        }
        return target;
      }
    
    	// **动态代理增强**
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          Set<Method> methods = signatureMap.get(method.getDeclaringClass());
          if (methods != null && methods.contains(method)) {
            return interceptor.intercept(new Invocation(target, method, args));
          }
          return method.invoke(target, args);
        } catch (Exception e) {
          throw ExceptionUtil.unwrapThrowable(e);
        }
      }
    	
    	// 省略getSignatureMap,getAllInterfaces方法
    	...
    }
    
  5. wrap方法执行时需要先通过interceptor获取signatureMap。
    Signature

    Intercepts
    注解中的value值注解,由于此value的返回值是数组,所以Signature会多个存在,最后解析出的结果便为signatureMap。

    Signature
    注解的作用为标注被动态代理的对象,具体的类型(class),具体的方法,方法具体的参数。只有特定类型和方法才会执行Interceptor方法。

    public class Plugin implements InvocationHandler {
    
      private final Object target;
      private final Interceptor interceptor;
      private final Map<Class<?>, Set<Method>> signatureMap;
    
      private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
      }
    
      private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        **// 通过此代码可发现,实现Interceptor的类必须添加Intercepts注解**
        if (interceptsAnnotation == null) {
          throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
        }
        Signature[] sigs = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
        for (Signature sig : sigs) {
          Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
          try {
    	      **// 通过Siganture的method与args,反射出Method对象,将其添加到map中
    	      // 作用是在执行动态代理invoke方法时,判断当前方法是否需要被interceptor执行**
            Method method = sig.type().getMethod(sig.method(), sig.args());
            methods.add(method);
          } catch (NoSuchMethodException e) {
            throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
          }
        }
        return signatureMap;
      }
    
    }
    
  6. 通过阅读源码可知,开发者需要自己实现
    Interceptor
    ,标记
    Intercepts
    注解,指定需要拦截的类、方法名,方法上的参数类型。并将Interceptor注册为Spring Bean。即可在interceptor方法中编写具体拦截代码。

实例

背景:在项目上为每一个需要插入至数据库中的实例对象,初始化id。

代码:

@Component
**// 拦截Executor类的update方法,该update方法会执行insert、update、delete操作**
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
public class MybatisUpdateInterceptor implements Interceptor {

	// 雪花算法id生成器
	@Autowired
	private IdGenerator idGenerator;

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		Method method = invocation.getMethod();
		MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
		**// 判断是否为insert方法**
		if (ms.getSqlCommandType() != SqlCommandType.INSERT) {
			return invocation.proceed();
		}
		BaseEntity entity = (BaseEntity) invocation.getArgs()[1];
		if (entity.getId() == null) {
			entity.setId(idGenerator.generate());
		}
		return method.invoke(invocation.getTarget(), invocation.getArgs());
	}

}

总结

项目开发者可灵活运用plugin,为数据库操作进行增强。日常开发中也可借鉴此流程,通过动态代理方式设计拦截/增强手段。

本文分享自华为云社区《
基于istio实现单集群地域故障转移
》,作者:可以交个朋友。

一 背景

随着应用程序的增长并变得更加复杂,微服务的数量也会增加,失败的可能性也会增加。微服务的故障可能多种原因造成,例如硬件问题、网络延迟、软件错误,甚至人为错误。故障转移Failover 是系统韧性设计中的一个基础能力,它们可以确保系统在出现故障时能够继续运行,并且能够在最小化的影响下进行恢复,减少或者消除对使用方或最终用户的影响,从而提高整个系统对外的可用性。

二 简介

云原生K8s、istio默认使用node上特定label作为地域信息:

  • 地区:代表较大的地理区域,例如 us-east。一个地区通常包含许多可用区。 在 Kubernetes 中,标签topology.kubernetes.io/region 决定了节点所在的地区。
  • 区域:区域内的一组计算资源。通过在区域内的多个区域中运行服务,可以在区域内的区域之间进行故障转移, 同时保持最终用户的数据地域性。在 Kubernetes 中,标签topology.kubernetes.io/zone决定了节点所在的区域。
  • 分区:允许管理员进一步细分区域,以实现更细粒度的控制,例如“相同机架”。 Kubernetes 中不存在分区的概念。所以 Istio 引入了自定义节点标签 topology.istio.io/subzone 来定义分区。

kubectl describe node xxx |grep topo

cke_114.png

如下图所示演示环境,helloworld作为服务端有多个实例分别部署在不同zone中(不同zone节点topology.kubernetes.io/zone的label不同)。通过istio的destinationrule中localityLbSetting.failover(故障转移策略)和outlierDetection(故障异常点检测),可以实现客户端业务访问helloworld服务时候,优先访问与客户端同可用区的服务端,当同可用区的helloworld服务端全部故障后,再访问指定可用区的服务端,实现故障转移。

cke_115.png

三 实战演练

事先准备好kubernetes+istio作为操作环境。可用华为云CCE和ASM服务进行操作。

3.1 部署服务端

1.创建sample 命名空间,并设置istio-proxy sidecar自动注入

apiVersion: v1
kind: Namespace
metadata:
name: sample
labels:
istio
-injection: enabled

2.部署helloworld服务 作为服务端

将根据以下脚本生成对yaml配置清单

#!/bin/bashset -euo pipefail

display_usage() {
echo
echo
"USAGE: ./gen-helloworld.sh [--version] [--includeService value] [--includeDeployment value]"echo"-h|--help: Prints usage information"echo"--version: Specifies the version that will be returned by the helloworld service, default: 'v1'"echo"--includeService: If 'true' the service will be included in the YAML, default: 'true'"echo"--includeDeployment: If 'true' the deployment will be included in the YAML, default: 'true'"}

INCLUDE_SERVICE
=${INCLUDE_SERVICE:-"true"}
INCLUDE_DEPLOYMENT
=${INCLUDE_DEPLOYMENT:-"true"}
SERVICE_VERSION
=${SERVICE_VERSION:-"v1"}while (( "$#" )); do case "$1" in -h|--help)
display_usage
exit
0;;--version)
SERVICE_VERSION
=$2shift2;;--includeService)
INCLUDE_SERVICE
=$2shift2;;--includeDeployment)
INCLUDE_DEPLOYMENT
=$2shift2;;*)
echo
"Error: Unsupported flag $1" >&2display_usage
exit
1;;
esac
done

SERVICE_YAML
=$(cat <<EOF
apiVersion: v1
kind: Service
metadata:
name: helloworld
labels:
app: helloworld
service: helloworld
spec:
ports:
- port: 5000name: http
selector:
app: helloworld
EOF
)

DEPLOYMENT_YAML
=$(cat <<EOF
apiVersion: apps
/v1
kind: Deployment
metadata:
name: helloworld
-${SERVICE_VERSION}
labels:
app: helloworld
version: ${SERVICE_VERSION}
spec:
replicas:
1selector:
matchLabels:
app: helloworld
version: ${SERVICE_VERSION}
template:
metadata:
labels:
app: helloworld
version: ${SERVICE_VERSION}
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
-matchExpressions:- key: topology.istio.io/subzoneoperator: In
values:
-${SERVICE_VERSION}

containers:
-name: helloworld
env:
-name: SERVICE_VERSION
value: ${SERVICE_VERSION}
image: docker.io
/istio/examples-helloworld-v1
resources:
requests:
cpu:
"100m"imagePullPolicy: IfNotPresent
ports:
- containerPort: 5000EOF
)

OUT
=""# Add the service to the output.if [[ "$INCLUDE_SERVICE" == "true"]]; then
OUT
="${SERVICE_YAML}"fi

# Add the deployment to the output.
if [[ "$INCLUDE_DEPLOYMENT" == "true"]]; then
# Add a separator
if [[ -n "$OUT"]]; then
OUT
+="--- "fi
OUT
+="${DEPLOYMENT_YAML}"fi

echo
"$OUT"

执行脚本: for LOC in "beijing" "tianjin" "shenyang"; do ./genHelloWorld.sh --version "$LOC" > "helloworld-${LOC}.yaml"; done 将会生成yaml配置清单,应用到集群即可。

kubectl apply -f helloworld-xxx.yaml -n sample

cke_116.png

3.2 部署客户端

kubectl apply -f sleep.yaml -n sample

# Sleep service
##################################################################################################
apiVersion: v1
kind: ServiceAccount
metadata:
name: sleep
---apiVersion: v1
kind: Service
metadata:
name: sleep
labels:
app: sleep
service: sleep
spec:
ports:
- port: 80name: http
selector:
app: sleep
---apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
replicas:
1selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
terminationGracePeriodSeconds:
0serviceAccountName: sleep
containers:
-name: sleep
image: curlimages
/curl
command: [
"/bin/sleep", "infinity"]
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /etc/sleep/tls
name: secret
-volume
volumes:
- name: secret-volume
secret:
secretName: sleep
-secret
optional:
true ---

cke_117.png

查看客户端中的存储的cluster信息

kubectl exec -it sleep-xxx -c istio-proxy -n sample -- curl localhost:15000/clusters可以看到cluster信息中包含了实例的PodIP和位置信息

cke_118.png

3.3 配置服务端地域故障转移规则

istio的流量治理一般都是通过virtualservice、destinationrule 、envoyfilter等来实现,其中地域故障转移是通过destinationrule配置实现的。因为在destinationrule中可以配置outerlineDecetion进行异常点检测,只有检测到异常后,才会进行故障转移。kubectl apply -f xxx.yaml

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: helloworld
namespace: sample
spec:
host: helloworld.sample.svc.cluster.local
trafficPolicy:
connectionPool:
http:
maxRequestsPerConnection:
1loadBalancer:
simple: ROUND_ROBIN
localityLbSetting: #开启地域负载均衡
enabled:
truefailover: #配置故障转移策略,failover主要控制Region等上层位置的切换- from: cn-north-4to: cn-south-1outlierDetection: #异常点检测
consecutive5xxErrors:
1interval: 1s
baseEjectionTime: 1m

以上治理策略表示:

  • 异常点检测:当某个客户端访问helloworld服务时,客户端对应的envoy会根据本次访问HTTP状态码对转发的服务端进行故障检测,故障检测条件为当发生1次5xx错误时实例就会被隔离1m。
  • 故障隔离:当指定region的所有后端实例均不正常,触发故障转移到下一个地域,确保了超出地区边界的故障转移将具有可预测的行为。如果位于cn-north-4 region的实例异常,流量就会发往cn-south-1 region 的实例。

3.4 验证地域负载均衡

通过位于cn-north-4/cn-north-4b/tianjin 的Sleep Pod 多次调用 HelloWorld 服务,均访问成功。

cke_119.png

同时可以发现服务端响应的Pod总是同一个

cke_120.png

查看sleep实例的proxy日志,通过日志中的%UPSTREAM_HOST%字段(红框标准)172.16.0.136,可以看到5个请求均被发送到相同的子区域helloworld实例(Pod IP为172.16.0.136)。

cke_121.png

这是因为istio考虑到网络开销,部署在region1/zone1上的sleep实例 大多时候只会访问部署在同Region同Zone的helloworld实例。

3.5 验证地域故障转移

首先模拟故障,通过下述命令 向 enovy 的 admin port 发送请求,关闭envoy的 listener 。enovy 收到请求后,会取消端口监听,不再接收新的连接和请求。

kubectl exec helloworld-tianjin-xxx -n sample -c istio-proxy -- curl -sSL -X POST 127.0.0.1:15000/drain_listeners

cke_122.png

再次通过位于cn-north-4/cn-north-4b/tianjin 的Sleep Pod 多次调用 HelloWorld 服务。

cke_123.png

可以发现4个请求被以轮询的方式发往cn-north-4/cn-north-4b/beijing 和cn-north-4/cn-north-4b/shenyang的 helloworld实例。以上结果说明,在一个区域的服务实例发生故障时,可根据配置,将请求路由到其它地域的服务实例进行处理,增强服务的可靠性。在实践中可通过From、To 配置region地区信息,控制在不同地区的实例上进行故障转移。

四 备注

关于地域负载均衡的配置failover主要控制的是跨region的场景,因为位于region内的zone或者subzone 上的实例默认就可以切换流量。本文档的实践主要是在region内进行操作的,所以不能演示完整的跨地域故障转移。一般也多用在多集群的治理环境中。

cke_124.png

点击关注,第一时间了解华为云新鲜技术~

本文介绍基于
Python

ArcPy
模块,实现
Excel数据读取

生成矢量图层
,同时进行
IDW插值

批量掩膜
的方法。

1 任务需求

首先,我们来明确一下本文所需实现的需求。

现有一个记录有北京市
部分PM2.5浓度监测站点
在2019年05月18日00时至23时(其中不含19时)等
23个逐小时PM2.5浓度数据

Excel
表格文件,我们需要将其中的数据依次读入一个包含北京市
各PM2.5浓度监测站点
的矢量点要素图层中;随后,基于这些站点导入的
23
个逐小时
PM2.5
浓度数据,逐小时对北京市
PM2.5
浓度加以反距离加权(
IDW
)方法的插值,即共绘制
23
幅插值图;最后,基于已有的北京市边界矢量数据,分别对这
23
幅插值图加以掩膜。

在这里,包含北京市
各PM2.5浓度监测站点
的矢量点要素图层是基于
Python基于Excel生成矢量图层及属性表信息:ArcPy
得到的,如下图所示。

image

其中,该矢量图层还包括属性表,属性表内容包括每一个站点的编号、地理位置与中文名称,如下图所示。

而记录有北京市
部分PM2.5浓度监测站点
在2019年05月18日00时至23时(其中不含19时)等
23个逐小时PM2.5浓度数据

Excel
表格文件则如下所示,其中包括各站点在23个整点时所监测到的
PM2.5
浓度。

2 代码实现

了解了需求后,我们就基于
Python
中的
ArcPy
模块,进行详细代码的撰写与介绍。

这里需要说明的是:在编写代码的时候,为了方便执行,所以希望代码后期可以在
ArcMap
中直接通过工具箱运行,即用到
Python程序脚本新建工具箱与自定义工具
的方法;因此,代码中对于一些需要初始定义的变量,都用到了
arcpy.GetParameterAsText()
函数。大家如果只是希望在
IDLE
中运行代码,那么直接对这些变量进行具体赋值即可。关于
Python程序脚本新建工具箱与自定义工具
,大家可以查看
ArcMap将Python写的代码转为工具箱与自定义工具
详细了解。

上面提到需要初始定义的变量一共有十个,其中
arcpy.env.workspace
参数表示当前工作空间,
csv_path
参数表示存储有北京市2019年05月18日00时至23时(其中不含19时)逐小时PM2.5浓度数据的
.csv
文件,
shape_file_path
参数表示站点信息矢量数据文件,
boundary_file_path
参数表示投影后北京市边界矢量数据文件,
spatial_resolution
参数表示
IDW
插值结果栅格图的像元大小,
power
参数表示
IDW
插值时所用距离的幂指数,
look_point
参数表示
IDW
插值时所用最邻近输入采样点数量的整数值,
max_distance
参数表示
IDW
插值时对最邻近输入采样点的限制距离,单位依据地图坐标系确定;
idw_result_dir
参数表示
IDW
插值结果图层保存路径,
mask_result_dir
参数表示
IDW
插值结果图层经掩膜后保存路径。

代码的整体思路为:首先利用
pd.read_csv
函数读取记录有北京市
部分PM2.5浓度监测站点
在2019年05月18日00时至23时(其中不含19时)等
23个逐小时PM2.5浓度数据

Excel
表格文件数据,随后在北京市
各PM2.5浓度监测站点
的矢量点要素图层的属性表中新建
23
个列,每一个列表示该监测站点在某一时刻的浓度数据(共有
23
个时刻,因此共有
23
个列);其次,由于矢量要素图层中的部分站点在
Excel
文件中并没有数据,因此需要将这些站点从矢量要素图层中删除;最后,分别利用
Idw
函数与
ExtractByMask
函数进行
IDW
插值与掩膜。

具体代码如下。

# -*- coding: utf-8 -*-
# @author: ChuTianjia

import copy
import arcpy
import pandas as pd
from arcpy.sa import *

arcpy.env.workspace=arcpy.GetParameterAsText(0)
csv_path=arcpy.GetParameterAsText(1)
shape_file_path=arcpy.GetParameterAsText(2)
idw_result_dir=arcpy.GetParameterAsText(8)
boundary_file_path=arcpy.GetParameterAsText(3)
mask_result_dir=arcpy.GetParameterAsText(9)
spatial_resolution=arcpy.GetParameterAsText(4)
power=arcpy.GetParameterAsText(5)
look_point=arcpy.GetParameterAsText(6)
max_distance=arcpy.GetParameterAsText(7)

csv_data=pd.read_csv(csv_path,header=0,encoding="gbk")
column_name_list=list(csv_data)
hour_column=csv_data["hour"]

pm_25_list=[[0]*len(csv_data) for i in range(csv_data.shape[1]-3)]

for i in range(3,csv_data.shape[1]):
    for index,data in csv_data.iterrows():
        pm_25_list[i-3][index]=data[i]

field_list=["hour_00","hour_01","hour_02","hour_03","hour_04","hour_05",\
            "hour_06","hour_07","hour_08","hour_09","hour_10",\
            "hour_11","hour_12","hour_13","hour_14","hour_15",\
            "hour_16","hour_17","hour_18","hour_20",\
            "hour_21","hour_22","hour_23"]
field_list_use=copy.deepcopy(field_list)
field_list_use.insert(0,"Name")

# Update the columns in the attribute table
for i in range(len(field_list)):
    arcpy.AddField_management(shape_file_path,field_list[i],"SHORT")

delete_num=0
delete_name=[]
with arcpy.da.UpdateCursor(shape_file_path,field_list_use) as cursor:
    for row in cursor:
        for column_name in column_name_list:
            if column_name==row[0]:
                for i in range(len(csv_data[column_name])):
                    row[i+1]=csv_data[column_name][i]
                    cursor.updateRow(row)

        # Find stations that without any data        
        if row[0] not in column_name_list:
            cursor.deleteRow()
            delete_num+=1
            delete_name.append(row[0])

arcpy.AddWarning("Delete {0} site(s) that do not contain any data, and the site(s) name is(are):".format(delete_num))
for i in delete_name:
    arcpy.AddMessage(i)
arcpy.AddMessage("\n")

# Perform IDW interpolation
arcpy.env.extent=boundary_file_path
for i in range(len(field_list)):
    idw_result=Idw(shape_file_path,field_list[i],spatial_resolution,power,RadiusVariable(look_point,max_distance))
    idw_result_path=idw_result_dir+"\\"+"BJ_"+field_list[i]+".tif"
    idw_result.save(idw_result_path)
    arcpy.AddMessage("{0} has completed IDW interpolation.".format(field_list[i]))

# Perform mask
tif_file_list=arcpy.ListRasters("BJ_hour_*","TIF")
for raster in tif_file_list:
    mask_result=ExtractByMask(raster,boundary_file_path)
    mask_result_path=mask_result_dir+"\\"+raster.strip(".tif")+"_Mask.tif"
    mask_result.save(mask_result_path)
    arcpy.AddMessage("{0} has been masked.".format(raster.strip(".tif")))

3 运行结果

执行上述代码,如果是在
ArcMap
中直接通过工具箱运行,则可以看到代码运行过程中出现的提示。

例如,下图所示提示可以知道有哪几个站点是没有数据、从而被剔除的。

下图则可以显示出目前代码的运行情况。

同时,在我们设定的结果文件夹中可以看到,
23
小时的插值图与掩膜图都将自动生成并保存在指定文件夹。

再来看看具体的图片长什么样子。

首先查看
IDW
插值结果图;我们以当日10时的插值结果图为例进行查看。可以看到其已对北京市边界矢量数据所包含的矩形范围完成了插值。

接下来,查看
IDW
插值结果图经过掩膜后的图像;我们同样以当日10时的插值、掩膜结果图为例进行查看。可以看到,经过掩膜操作后的图像已经完全符合北京市边界矢量数据的范围。

至此,大功告成。