1 | %%
|
---|
2 | % create kml linestrings for the partition edges.
|
---|
3 | %
|
---|
4 | % [kfold]=kml_part_edges(md,params)
|
---|
5 | %
|
---|
6 | % where the required input is:
|
---|
7 | % md (model, model class object)
|
---|
8 | %
|
---|
9 | % the optional input is:
|
---|
10 | % params (string/numeric, parameter names and values)
|
---|
11 | %
|
---|
12 | % and the optional input is:
|
---|
13 | % latsgn (numeric, +1/-1 for north/south latitude)
|
---|
14 | % data (numeric, element or nodal results data)
|
---|
15 | % alt (numeric, altitude for polygons, default 10000)
|
---|
16 | % cmin (numeric, minimum of color map)
|
---|
17 | % cmax (numeric, maximum of color map)
|
---|
18 | % cmap (char or numeric, colormap definition)
|
---|
19 | % prtplt (char, 'off'/'no' for partition segment plot)
|
---|
20 | %
|
---|
21 | % and the required output is:
|
---|
22 | % kfold (kml_folder, folder of linestring placemarks)
|
---|
23 | %
|
---|
24 | function [kfold]=kml_part_edges(varargin)
|
---|
25 |
|
---|
26 | if ~nargin
|
---|
27 | help kml_part_edges
|
---|
28 | return
|
---|
29 | end
|
---|
30 |
|
---|
31 | %% process input data
|
---|
32 |
|
---|
33 | iarg=1;
|
---|
34 | if (nargin >= 1)
|
---|
35 | md=varargin{1};
|
---|
36 | end
|
---|
37 | if ~exist('md','var') || isempty(md) || ~isa(md,'model')
|
---|
38 | error(['Model ''' inputname(iarg) ''' is unrecognized class ''' class(md) '''.']);
|
---|
39 | end
|
---|
40 |
|
---|
41 | % parameters
|
---|
42 |
|
---|
43 | iarg=iarg+1;
|
---|
44 | while (iarg <= nargin-1)
|
---|
45 | if ischar(varargin{iarg})
|
---|
46 | if ~isempty(strmatch(varargin{iarg},...
|
---|
47 | {'latsgn','data','alt',...
|
---|
48 | 'cmin','cmax','cmap','prtplt'},...
|
---|
49 | 'exact'))
|
---|
50 | eval([varargin{iarg} '=varargin{iarg+1};']);
|
---|
51 | disp([varargin{iarg} '=' any2str(varargin{iarg+1},20) ';']);
|
---|
52 | else
|
---|
53 | warning([varargin{iarg} '=' any2str(varargin{iarg+1},20) ' is not recognized.']);
|
---|
54 | end
|
---|
55 | else
|
---|
56 | error(['''' any2str(varargin{iarg}) ''' is not a parameter name.']);
|
---|
57 | end
|
---|
58 | if strcmpi(varargin{iarg},'data')
|
---|
59 | cdata=inputname(iarg+1);
|
---|
60 | end
|
---|
61 | iarg=iarg+2;
|
---|
62 | end
|
---|
63 |
|
---|
64 | if isempty(md.mesh.lat) || ((numel(md.mesh.lat) == 1) && isnan(md.mesh.lat)) || ...
|
---|
65 | isempty(md.mesh.long) || ((numel(md.mesh.long) == 1) && isnan(md.mesh.long))
|
---|
66 | if ~exist('latsgn','var')
|
---|
67 | error(['Missing ''latsgn'' parameter to calculate missing lat/long data.']);
|
---|
68 | elseif (abs(latsgn) ~= 1)
|
---|
69 | error(['Incorrect latsgn=' num2str(latsgn) ' parameter to calculate missing lat/long data.']);
|
---|
70 | else
|
---|
71 | display('Converting x/y data to lat/long data.');
|
---|
72 | [md.mesh.lat,md.mesh.long]=xy2ll(md.x,md.y,latsgn);
|
---|
73 | end
|
---|
74 | end
|
---|
75 |
|
---|
76 | if exist('data','var') && ~isempty(data)
|
---|
77 | if (numel(data)==md.mesh.numberofelements)
|
---|
78 | edata=data;
|
---|
79 | elseif (numel(data)==md.mesh.numberofvertices)
|
---|
80 | ndata=data;
|
---|
81 | display('Averaging nodal data to element data.');
|
---|
82 | edata=zeros(1,md.mesh.numberofelements);
|
---|
83 | for i=1:size(md.mesh.elements,1)
|
---|
84 | for j=1:size(md.mesh.elements,2)
|
---|
85 | edata(i)=edata(i)+ndata(md.mesh.elements(i,j));
|
---|
86 | end
|
---|
87 | edata(i)=edata(i)/size(md.mesh.elements,2);
|
---|
88 | end
|
---|
89 | else
|
---|
90 | error(['Data has incorrect number of ' num2str(numel(data)) ' values.']);
|
---|
91 | end
|
---|
92 | end
|
---|
93 |
|
---|
94 | % colormap command operates on a figure, so create an invisible one
|
---|
95 | % (could also directly call colormaps, e.g. jet(64), but risky)
|
---|
96 |
|
---|
97 | hfig=figure('Visible','off');
|
---|
98 | if exist('cmap','var')
|
---|
99 | colormap(cmap)
|
---|
100 | end
|
---|
101 | cmap=colormap;
|
---|
102 | close(hfig)
|
---|
103 |
|
---|
104 | if exist('edata','var')
|
---|
105 | if ~exist('cmin','var')
|
---|
106 | cmin=min(min(edata));
|
---|
107 | end
|
---|
108 | if ~exist('cmax','var')
|
---|
109 | cmax=max(max(edata));
|
---|
110 | end
|
---|
111 | end
|
---|
112 |
|
---|
113 | if ~exist('alt','var')
|
---|
114 | alt=10000;
|
---|
115 | end
|
---|
116 |
|
---|
117 | %% write folder for partition edges
|
---|
118 |
|
---|
119 | if (~exist('prtplt','var') || strncmpi(prtplt,'on' ,2) || strncmpi(prtplt,'y',1)) && ...
|
---|
120 | md.qmu.numberofpartitions
|
---|
121 | kfold=kml_folder();
|
---|
122 | kfold.name ='Partition Edges';
|
---|
123 | kfold.visibility=1;
|
---|
124 | kfold.descript =sprintf('Partitions=%d, Nodes=%d',...
|
---|
125 | md.qmu.numberofpartitions,md.mesh.numberofvertices);
|
---|
126 | kfold.feature ={repmat(kml_placemark(),1,md.qmu.numberofpartitions)};
|
---|
127 |
|
---|
128 | % write each partition as a linestring multigeometry placemark
|
---|
129 |
|
---|
130 | disp(['Writing ' num2str(md.qmu.numberofpartitions) ' partitions as KML linestrings.']);
|
---|
131 | epart=md.qmu.partition(md.mesh.elements)+1;
|
---|
132 | if exist('ndata','var') || exist('edata','var')
|
---|
133 | pdata=zeros(1,md.qmu.numberofpartitions);
|
---|
134 | pdata(:)=NaN;
|
---|
135 | end
|
---|
136 |
|
---|
137 | % loop over each partition
|
---|
138 |
|
---|
139 | for k=1:md.qmu.numberofpartitions
|
---|
140 | % disp(['partition k=' int2str(k)])
|
---|
141 |
|
---|
142 | % for each partition, find all the included elements and determine the
|
---|
143 | % perimeter (including those shared by another partition)
|
---|
144 |
|
---|
145 | [icol,irow]=find(epart'==k);
|
---|
146 | if isempty(irow)
|
---|
147 | continue;
|
---|
148 | end
|
---|
149 | irow=unique(irow);
|
---|
150 | elemp=md.mesh.elements(irow,:);
|
---|
151 | epartp=epart(irow,:);
|
---|
152 | nodeconp=kmlnodeconnectivity(elemp,md.mesh.numberofvertices);
|
---|
153 | [edgeadjp]=edgeadjacency(elemp,nodeconp);
|
---|
154 | [edgeper,elemper,iloop]=edgeperimeter(elemp,nodeconp,edgeadjp);
|
---|
155 | iloop(end+1)=size(edgeper,1)+1;
|
---|
156 |
|
---|
157 | % determine the data to be used for the colors (if any)
|
---|
158 |
|
---|
159 | if exist('ndata','var')
|
---|
160 | pdata(k)=ndata(find(md.qmu.partition+1==k,1));
|
---|
161 | elseif exist('edata','var')
|
---|
162 | for i=1:size(epartp,1)
|
---|
163 | if isempty(find(epart(i,:)~=k,1))
|
---|
164 | pdata(k)=edata(irow(i));
|
---|
165 | break
|
---|
166 | end
|
---|
167 | end
|
---|
168 | if isnan(pdata(k))
|
---|
169 | warning('Data for Partition %d is not defined.\n',k)
|
---|
170 | end
|
---|
171 | end
|
---|
172 |
|
---|
173 | % set up the placemark with multigeometry
|
---|
174 |
|
---|
175 | kplace=kml_placemark();
|
---|
176 | if (length(iloop)-1 > 1)
|
---|
177 | kplace.name =sprintf('Partition %d (%d loops)',k,length(iloop)-1);
|
---|
178 | else
|
---|
179 | kplace.name =sprintf('Partition %d',k);
|
---|
180 | end
|
---|
181 | kplace.visibility=1;
|
---|
182 | if exist('pdata','var')
|
---|
183 | kplace.descript =sprintf('Partition data: %g',pdata(k));
|
---|
184 | imap = fix((pdata(k)-cmin)/(cmax-cmin)*size(cmap,1))+1;
|
---|
185 | if (imap >= 1) && (imap <= size(cmap,1))
|
---|
186 | kplace.styleurl =sprintf('#MatlabColor%d',imap);
|
---|
187 | elseif (pdata(k) == cmax)
|
---|
188 | kplace.styleurl =sprintf('#MatlabColor%d',size(cmap,1));
|
---|
189 | else
|
---|
190 | kplace.styleurl =sprintf('#BlackLineEmptyPoly');
|
---|
191 | end
|
---|
192 | else
|
---|
193 | kplace.styleurl =sprintf('#BlackLineRandomPoly');
|
---|
194 | end
|
---|
195 |
|
---|
196 | kmgeom=kml_multigeometry();
|
---|
197 | kmgeom.geometry ={repmat(kml_linestring(),1,length(iloop)-1)};
|
---|
198 |
|
---|
199 | % loop over each loop of the perimeter for the given partition
|
---|
200 |
|
---|
201 | for i=1:length(iloop)-1
|
---|
202 | kline=kml_linestring();
|
---|
203 | kline.extrude =1;
|
---|
204 | kline.tessellate=1;
|
---|
205 | kline.altmode ='relativeToGround';
|
---|
206 | kline.coords =zeros(0,3);
|
---|
207 |
|
---|
208 | elast=0;
|
---|
209 | nlast=0;
|
---|
210 | slast=0;
|
---|
211 | lat=[];
|
---|
212 | long=[];
|
---|
213 |
|
---|
214 | % loop over the element edges on the loop of the partition
|
---|
215 |
|
---|
216 | j=iloop(i);
|
---|
217 | while (j < iloop(i+1))
|
---|
218 | % find which side of element is referenced in perimeter list
|
---|
219 | for l=1:size(elemp,2)
|
---|
220 | if ((elemp(elemper(j),l) == edgeper(j,1)) && ...
|
---|
221 | (elemp(elemper(j),mod(l,3)+1) == edgeper(j,2))) || ...
|
---|
222 | ((elemp(elemper(j),l) == edgeper(j,2)) && ...
|
---|
223 | (elemp(elemper(j),mod(l,3)+1) == edgeper(j,1)))
|
---|
224 | jedge=l;
|
---|
225 | break
|
---|
226 | end
|
---|
227 | end
|
---|
228 |
|
---|
229 | % check if element side connects nodes in partition
|
---|
230 | if (epartp(elemper(j),jedge) == k) && ...
|
---|
231 | (epartp(elemper(j),mod(jedge,3)+1) == k)
|
---|
232 | % write out specified element side
|
---|
233 | % disp(['segment j=' int2str(j) ' unshared edge ' int2str(edgeper(j,1)) ' to ' int2str(edgeper(j,2)) ' on side ' int2str(jedge) ' from element ' int2str(elemper(j)) ' written.'])
|
---|
234 | % if first edge, write out first node
|
---|
235 | if ~elast
|
---|
236 | kline.coords(end+1,:)=[md.mesh.long(edgeper(j,1)) md.mesh.lat(edgeper(j,1)) alt];
|
---|
237 | end
|
---|
238 | kline.coords(end+1,:)=[md.mesh.long(edgeper(j,2)) md.mesh.lat(edgeper(j,2)) alt];
|
---|
239 | elast=elemper(j);
|
---|
240 | nlast=edgeper(j,2);
|
---|
241 | slast=0;
|
---|
242 | j=j+1;
|
---|
243 |
|
---|
244 | % element not entirely within partition, so figure out boundary
|
---|
245 | else
|
---|
246 | % disp(['segment j=' int2str(j) ' from element ' int2str(elemper(j)) ' shared by other partitions.'])
|
---|
247 | ielem=elemper(j);
|
---|
248 |
|
---|
249 | % follow partition boundary through elements not wholly in partition
|
---|
250 | % (may include elements not in perimeter list)
|
---|
251 |
|
---|
252 | while 1
|
---|
253 | % if first edge, figure out direction from perimeter edge direction
|
---|
254 | if ~nlast && ~slast
|
---|
255 | nlast=find(elemp(ielem,:)==edgeper(j,1));
|
---|
256 | nnext=find(elemp(ielem,:)==edgeper(j,2));
|
---|
257 | if (nlast+nnext == 3)
|
---|
258 | slast=1;
|
---|
259 | elseif (nlast+nnext == 5)
|
---|
260 | slast=2;
|
---|
261 | elseif (nlast+nnext == 4)
|
---|
262 | slast=3;
|
---|
263 | end
|
---|
264 | if (nnext+(6-nlast-nnext) == 3)
|
---|
265 | snext=1;
|
---|
266 | elseif (nnext+(6-nlast-nnext) == 5)
|
---|
267 | snext=2;
|
---|
268 | elseif (nnext+(6-nlast-nnext) == 4)
|
---|
269 | snext=3;
|
---|
270 | end
|
---|
271 |
|
---|
272 | % find how many nodes of current element are in current partition
|
---|
273 | % (1 or 2, not 3) and handle each permutation separately
|
---|
274 | ipart=find(epartp(ielem,:)==k);
|
---|
275 | % two nodes are in current partition, so cut off other node
|
---|
276 | if (length(ipart) == 2)
|
---|
277 | switch 6-sum(ipart)
|
---|
278 | case nlast
|
---|
279 | slast=6-slast-snext;
|
---|
280 | nlast=0;
|
---|
281 | case nnext
|
---|
282 | if (epartp(ielem,nnext) == k)
|
---|
283 | nlast=nnext;
|
---|
284 | end
|
---|
285 | otherwise
|
---|
286 | slast=6-slast-snext;
|
---|
287 | nlast=0;
|
---|
288 | end
|
---|
289 | % one node is in current partition
|
---|
290 | else
|
---|
291 | % all different, so cut through centroid
|
---|
292 | if (epartp(ielem,1) ~= epartp(ielem,2)) && ...
|
---|
293 | (epartp(ielem,2) ~= epartp(ielem,3)) && ...
|
---|
294 | (epartp(ielem,3) ~= epartp(ielem,1))
|
---|
295 | switch ipart
|
---|
296 | case {nlast,nnext}
|
---|
297 | if (epartp(ielem,nnext) == k)
|
---|
298 | nlast=nnext;
|
---|
299 | end
|
---|
300 | otherwise
|
---|
301 | slast=6-slast-snext;
|
---|
302 | nlast=0;
|
---|
303 | end
|
---|
304 | % other two are in the same partition, so cut them off
|
---|
305 | else
|
---|
306 | switch ipart
|
---|
307 | case nlast
|
---|
308 | if (epartp(ielem,nnext) == k)
|
---|
309 | nlast=nnext;
|
---|
310 | end
|
---|
311 | case nnext
|
---|
312 | slast=snext;
|
---|
313 | nlast=0;
|
---|
314 | otherwise
|
---|
315 | slast=6-slast-snext;
|
---|
316 | nlast=0;
|
---|
317 | end
|
---|
318 | end
|
---|
319 | end
|
---|
320 |
|
---|
321 | % last edge exited last element at node
|
---|
322 | if nlast
|
---|
323 | % write out first node of first side for half-edge to midpoint
|
---|
324 | % disp(['segment j=' int2str(j) ' unshared half edge from node ' int2str(elemp(ielem,nlast)) ' (node ' int2str(nlast) ') on side ' int2str(slast) ' from element ' int2str(ielem) ' written.'])
|
---|
325 | kline.coords(end+1,:)=[md.mesh.long(elemp(ielem,nlast)) ...
|
---|
326 | md.mesh.lat(elemp(ielem,nlast)) alt];
|
---|
327 | end
|
---|
328 | nlast=0;
|
---|
329 |
|
---|
330 | % write out midpoint of first side
|
---|
331 | kline.coords(end+1,:)=[(md.mesh.long(elemp(ielem,slast))...
|
---|
332 | +md.mesh.long(elemp(ielem,mod(slast,3)+1)))/2. ...
|
---|
333 | (md.mesh.lat(elemp(ielem,slast))...
|
---|
334 | +md.mesh.lat(elemp(ielem,mod(slast,3)+1)))/2. alt];
|
---|
335 | end
|
---|
336 |
|
---|
337 | % last edge exited last element at node
|
---|
338 | if nlast
|
---|
339 | if elast
|
---|
340 | % find where last node on previous element occurs on current element
|
---|
341 | nlast=find(elemp(ielem,:)==nlast,1);
|
---|
342 | end
|
---|
343 | % half-edge occurs on unshared side from current node (unique unless mesh
|
---|
344 | % is only attached at node)
|
---|
345 | switch nlast
|
---|
346 | case 1
|
---|
347 | if ~edgeadjp(ielem,1)
|
---|
348 | nnext=2;
|
---|
349 | slast=1;
|
---|
350 | else
|
---|
351 | nnext=3;
|
---|
352 | slast=3;
|
---|
353 | end
|
---|
354 | case 2
|
---|
355 | if ~edgeadjp(ielem,2)
|
---|
356 | nnext=3;
|
---|
357 | slast=2;
|
---|
358 | else
|
---|
359 | nnext=1;
|
---|
360 | slast=1;
|
---|
361 | end
|
---|
362 | case 3
|
---|
363 | if ~edgeadjp(ielem,3)
|
---|
364 | nnext=1;
|
---|
365 | slast=3;
|
---|
366 | else
|
---|
367 | nnext=2;
|
---|
368 | slast=2;
|
---|
369 | end
|
---|
370 | end
|
---|
371 | % write out half-edge from current node to midpoint of unshared side
|
---|
372 | % disp(['segment j=' int2str(j) ' unshared half edge from node ' int2str(elemp(ielem,nlast)) ' (node ' int2str(nlast) ') on side ' int2str(slast) ' from element ' int2str(ielem) ' written.'])
|
---|
373 | kline.coords(end+1,:)=[(md.mesh.long(elemp(ielem,nlast))...
|
---|
374 | +md.mesh.long(elemp(ielem,nnext)))/2. ...
|
---|
375 | (md.mesh.lat(elemp(ielem,nlast))...
|
---|
376 | +md.mesh.lat(elemp(ielem,nnext)))/2. alt];
|
---|
377 | nlast=0;
|
---|
378 |
|
---|
379 | % last edge exited last element at midpoint of side
|
---|
380 | elseif slast
|
---|
381 | if elast
|
---|
382 | % find where last side on previous element occurs on current element
|
---|
383 | slast=find(edgeadjp(ielem,:)==elast,1);
|
---|
384 | end
|
---|
385 | end
|
---|
386 |
|
---|
387 | % find how many nodes of current element are in current partition
|
---|
388 | % (1 or 2, not 3) and handle each permutation separately
|
---|
389 | ipart=find(epartp(ielem,:)==k);
|
---|
390 | if (length(ipart) == 2)
|
---|
391 | % two nodes are in current partition, so cut off other node
|
---|
392 | switch 6-sum(ipart)
|
---|
393 | case 1
|
---|
394 | snext=3+1-slast;
|
---|
395 | case 2
|
---|
396 | snext=1+2-slast;
|
---|
397 | case 3
|
---|
398 | snext=2+3-slast;
|
---|
399 | end
|
---|
400 | else
|
---|
401 | if (epartp(ielem,1) ~= epartp(ielem,2)) && ...
|
---|
402 | (epartp(ielem,2) ~= epartp(ielem,3)) && ...
|
---|
403 | (epartp(ielem,3) ~= epartp(ielem,1))
|
---|
404 | % all different, so cut through centroid
|
---|
405 | % disp(['element ielem=' int2str(ielem) ' centroid written.'])
|
---|
406 | kline.coords(end+1,:)=[sum(md.mesh.long(elemp(ielem,:)))/3. ...
|
---|
407 | sum(md.mesh.lat(elemp(ielem,:)))/3. alt];
|
---|
408 | end
|
---|
409 | % one node is in current partition, so cut off other two nodes
|
---|
410 | switch ipart
|
---|
411 | case 1
|
---|
412 | snext=3+1-slast;
|
---|
413 | case 2
|
---|
414 | snext=1+2-slast;
|
---|
415 | case 3
|
---|
416 | snext=2+3-slast;
|
---|
417 | end
|
---|
418 | end
|
---|
419 | % write out midpoint of opposite side
|
---|
420 | % disp(['segment j=' int2str(j) ' internal edge from side ' int2str(slast) ' to side ' int2str(snext) ' from element ' int2str(ielem) ' written.'])
|
---|
421 | kline.coords(end+1,:)=[(md.mesh.long(elemp(ielem,snext))...
|
---|
422 | +md.mesh.long(elemp(ielem,mod(snext,3)+1)))/2. ...
|
---|
423 | (md.mesh.lat(elemp(ielem,snext))...
|
---|
424 | +md.mesh.lat(elemp(ielem,mod(snext,3)+1)))/2. alt];
|
---|
425 | elast=ielem;
|
---|
426 | nlast=0;
|
---|
427 | slast=snext;
|
---|
428 | % find adjacent element to opposite side
|
---|
429 | ielem=edgeadjp(elast,slast);
|
---|
430 | % if opposite side is unshared, find it in edge perimeter list
|
---|
431 | if ~ielem
|
---|
432 | jlast=find(elemper(j:end)==elast)+j-1;
|
---|
433 | j=0;
|
---|
434 | for l=1:length(jlast)
|
---|
435 | if ((elemp(elast,slast) == edgeper(jlast(l),1)) && ...
|
---|
436 | (elemp(elast,mod(slast,3)+1) == edgeper(jlast(l),2))) || ...
|
---|
437 | ((elemp(elast,slast) == edgeper(jlast(l),2)) && ...
|
---|
438 | (elemp(elast,mod(slast,3)+1) == edgeper(jlast(l),1)))
|
---|
439 | j=jlast(l);
|
---|
440 | break
|
---|
441 | end
|
---|
442 | end
|
---|
443 | if ~j
|
---|
444 | j=iloop(i+1)-1;
|
---|
445 | end
|
---|
446 | % write out half-edge from midpoint of unshared side to node
|
---|
447 | if (epartp(elast,slast) == k)
|
---|
448 | nnext=slast;
|
---|
449 | else
|
---|
450 | nnext=mod(slast,3)+1;
|
---|
451 | end
|
---|
452 | % disp(['segment j=' int2str(j) ' unshared half edge on side ' int2str(slast) ' to node ' int2str(elemp(elast,nnext)) ' (node ' int2str(nnext) ') from element ' int2str(elast) ' written.'])
|
---|
453 | kline.coords(end+1,:)=[md.mesh.long(elemp(elast,nnext)) ...
|
---|
454 | md.mesh.lat(elemp(elast,nnext)) alt];
|
---|
455 | break
|
---|
456 | % if not unshared, advance perimeter list and watch for end
|
---|
457 | else
|
---|
458 | if (elast == elemper(j))
|
---|
459 | if (j+1 < iloop(i+1)) && ...
|
---|
460 | ~isempty(find(elemper(j+1:end)~=elast,1))
|
---|
461 | j=j+find(elemper(j+1:end)~=elast,1);
|
---|
462 | else
|
---|
463 | break
|
---|
464 | end
|
---|
465 | end
|
---|
466 | end
|
---|
467 | end
|
---|
468 | j=j+1;
|
---|
469 | end
|
---|
470 | end
|
---|
471 |
|
---|
472 | kmgeom.geometry{1}(i)=kline;
|
---|
473 | clear kline
|
---|
474 | end
|
---|
475 |
|
---|
476 | kplace.geometry=kmgeom;
|
---|
477 | kfold.feature{1}(k)=kplace;
|
---|
478 | clear kmgeom kplace
|
---|
479 | end
|
---|
480 | end
|
---|
481 |
|
---|
482 | end
|
---|