Statistics
| Revision:

root / org.gvsig.legend.urbanhorizontalsignage / trunk / org.gvsig.legend.urbanhorizontalsignage / org.gvsig.legend.urbanhorizontalsignage.lib / org.gvsig.legend.urbanhorizontalsignage.lib.impl / src / main / java / org / gvsig / legend / urbanhorizontalsignage / lib / impl / DefaultUrbanHorizontalSignageManager.java @ 5139

History | View | Annotate | Download (23.8 KB)

1
/* gvSIG. Desktop Geographic Information System.
2
 *
3
 * Copyright ? 2007-2015 gvSIG Association
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License
7
 * as published by the Free Software Foundation; either version 2
8
 * of the License, or (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18
 * MA  02110-1301, USA.
19
 *
20
 * For any additional information, do not hesitate to contact us
21
 * at info AT gvsig.com, or visit our website www.gvsig.com.
22
 */
23
package org.gvsig.legend.urbanhorizontalsignage.lib.impl;
24

    
25
import java.io.File;
26
import java.util.ArrayList;
27
import java.util.Iterator;
28
import java.util.List;
29
import java.util.UUID;
30
import org.gvsig.fmap.dal.DALLocator;
31
import org.gvsig.fmap.dal.DataManager;
32
import org.gvsig.fmap.dal.DataStoreParameters;
33
import org.gvsig.fmap.dal.feature.EditableFeature;
34
import org.gvsig.fmap.dal.feature.EditableFeatureAttributeDescriptor;
35
import org.gvsig.fmap.dal.feature.EditableFeatureType;
36
import org.gvsig.fmap.dal.feature.Feature;
37
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
38
import org.gvsig.fmap.dal.feature.FeatureSet;
39
import org.gvsig.fmap.dal.feature.FeatureStore;
40
import org.gvsig.fmap.dal.store.jdbc.JDBCNewStoreParameters;
41
import org.gvsig.fmap.dal.store.jdbc.JDBCServerExplorerParameters;
42
import org.gvsig.fmap.dal.store.jdbc2.JDBCServerExplorer;
43
import org.gvsig.fmap.geom.Geometry;
44
import static org.gvsig.fmap.geom.Geometry.JOIN_STYLE_MITRE;
45
import static org.gvsig.fmap.geom.Geometry.JOIN_STYLE_ROUND;
46
import org.gvsig.fmap.geom.GeometryLocator;
47
import org.gvsig.fmap.geom.GeometryManager;
48
import org.gvsig.fmap.geom.aggregate.MultiLine;
49
import org.gvsig.fmap.geom.aggregate.MultiPolygon;
50
import org.gvsig.fmap.geom.exception.CreateGeometryException;
51
import org.gvsig.fmap.geom.operation.GeometryOperationException;
52
import org.gvsig.fmap.geom.operation.GeometryOperationNotSupportedException;
53
import org.gvsig.fmap.geom.primitive.Line;
54
import org.gvsig.fmap.geom.primitive.Point;
55
import org.gvsig.fmap.geom.type.GeometryType;
56
import org.gvsig.legend.urbanhorizontalsignage.lib.api.UrbanHorizontalSignageConfig;
57
import org.gvsig.legend.urbanhorizontalsignage.lib.api.UrbanHorizontalSignageData;
58
import static org.gvsig.legend.urbanhorizontalsignage.lib.api.UrbanHorizontalSignageData.CONTINUITY_MODE_CONT;
59
import static org.gvsig.legend.urbanhorizontalsignage.lib.api.UrbanHorizontalSignageData.CONTINUITY_MODE_CONT_CONT;
60
import static org.gvsig.legend.urbanhorizontalsignage.lib.api.UrbanHorizontalSignageData.CONTINUITY_MODE_CONT_DISC;
61
import static org.gvsig.legend.urbanhorizontalsignage.lib.api.UrbanHorizontalSignageData.CONTINUITY_MODE_DISC;
62
import static org.gvsig.legend.urbanhorizontalsignage.lib.api.UrbanHorizontalSignageData.CONTINUITY_MODE_DISC_CONT;
63
import static org.gvsig.legend.urbanhorizontalsignage.lib.api.UrbanHorizontalSignageData.CONTINUITY_MODE_DISC_DISC;
64
import org.gvsig.legend.urbanhorizontalsignage.lib.api.UrbanHorizontalSignageLegend;
65
import org.gvsig.legend.urbanhorizontalsignage.lib.api.UrbanHorizontalSignageLocator;
66
import org.gvsig.legend.urbanhorizontalsignage.lib.api.UrbanHorizontalSignageManager;
67
import org.gvsig.tools.ToolsLocator;
68
import org.gvsig.tools.folders.FoldersManager;
69
import org.gvsig.tools.task.SimpleTaskStatus;
70
import org.gvsig.tools.util.HasAFile;
71
import org.slf4j.LoggerFactory;
72

    
73
public class DefaultUrbanHorizontalSignageManager implements UrbanHorizontalSignageManager {
74

    
75
    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(DefaultUrbanHorizontalSignageManager.class);
76

    
77
    @Override
78
    public UrbanHorizontalSignageLegend create() {
79
        return new DefaultUrbanHorizontalSignageLegend();
80
    }
81

    
82
    @Override
83
    public Class<? extends UrbanHorizontalSignageLegend> getLegendClass() {
84
        return DefaultUrbanHorizontalSignageLegend.class;
85
    }
86

    
87
    @Override
88
    public UrbanHorizontalSignageData createUrbanHorizontalSignageData() {
89
        return new DefaultUrbanHorizontalSignageData();
90
    }
91

    
92
    @Override
93
    public void calculateGeometries(Geometry originalGeometry, UrbanHorizontalSignageData data) {
94
        GeometryManager geomManager = GeometryLocator.getGeometryManager();
95
        try {
96
            MultiPolygon segments = geomManager.createMultiPolygon(originalGeometry.getGeometryType().getSubType());
97
            MultiPolygon holes = geomManager.createMultiPolygon(originalGeometry.getGeometryType().getSubType());
98
            MultiLine lines = originalGeometry.toLines();
99
            final double offsetValueInMeters = data.getGapWidth()/2.0+data.getWidth()/2.0;
100
            final double bufferValueInMeters = data.getWidth() / 2.0;
101
            for (Geometry geom : lines) {
102
                Line line = (Line) geom;
103
                switch (data.getContinuity()) {
104
                    case CONTINUITY_MODE_CONT:
105
                    default:
106
                        Geometry buffer = line.buffer(
107
                                bufferValueInMeters,
108
                                data.isRoundVertex() ? JOIN_STYLE_ROUND : JOIN_STYLE_MITRE,
109
                                true
110
                        );
111
                        segments.addPrimitives(buffer);
112
                        break;
113
                    
114
                    case CONTINUITY_MODE_DISC:
115
                        SplittedLine splittedLine = splitLine(line, data.getSegmentsLength(), data.getHolesLength());
116
                        List<Line> splittedSegments = splittedLine.getSegments();
117
                        List<Line> splittedHoles = splittedLine.getHoles();
118
                        for (Line segment : splittedSegments) {
119
                            buffer = segment.buffer(bufferValueInMeters,
120
                                    data.isRoundVertex() ? JOIN_STYLE_ROUND : JOIN_STYLE_MITRE,
121
                                    true
122
                            );
123
                            segments.addPrimitives(buffer);
124
                        }
125
                        for (Line hole : splittedHoles) {
126
                            buffer = hole.buffer(
127
                                    bufferValueInMeters,
128
                                    data.isRoundVertex() ? JOIN_STYLE_ROUND : JOIN_STYLE_MITRE,
129
                                    true
130
                            );
131
                            holes.addPrimitives(buffer);
132
                        }
133
                        break;
134
                        
135
                    case CONTINUITY_MODE_CONT_CONT:
136
                        /* The left and right sides would be indistinguishable but 
137
                        I perform the same calculations as in the cases below 
138
                        to maintain semantic coherence */
139
                        
140
                        //Left
141
                        if (line.isClosed() && line.getNumVertices() > 2 && line.isCCW()) {
142
                            Line cloned = line.cloneGeometry();
143
                            cloned.flip();
144
                            addOffsetedAndBufferedSegment(segments, cloned, offsetValueInMeters, bufferValueInMeters, data.isRoundVertex());
145
                        } else {
146
                            addOffsetedAndBufferedSegment(segments, line, -offsetValueInMeters, bufferValueInMeters, data.isRoundVertex());
147
                        }
148
                        //Right
149
                        if (line.isClosed() && line.getNumVertices() > 2 && line.isCCW()) {
150
                            Line cloned = line.cloneGeometry();
151
                            cloned.flip();
152
                            addOffsetedAndBufferedSegment(segments, cloned, -offsetValueInMeters, bufferValueInMeters, data.isRoundVertex());
153
                        } else {
154
                            addOffsetedAndBufferedSegment(segments, line, offsetValueInMeters, bufferValueInMeters, data.isRoundVertex());
155
                        }
156
                        break;
157
                        
158
                    case CONTINUITY_MODE_CONT_DISC:
159
                        //Left
160
                        if (line.isClosed() && line.getNumVertices() > 2 && line.isCCW()) {
161
                            Line cloned = line.cloneGeometry();
162
                            cloned.flip();
163
                            addOffsetedAndBufferedSegment(segments, cloned, offsetValueInMeters, bufferValueInMeters, data.isRoundVertex());
164
                        } else {
165
                            addOffsetedAndBufferedSegment(segments, line, -offsetValueInMeters, bufferValueInMeters, data.isRoundVertex());
166
                        }
167
                        //Right
168
                        splittedLine = splitLine(line, data.getSegmentsLength(), data.getHolesLength());
169
                        splittedSegments = splittedLine.getSegments();
170
                        splittedHoles = splittedLine.getHoles();
171
                        for (Line segment : splittedSegments) {
172
                            addOffsetedAndBufferedSegment(segments, segment, offsetValueInMeters, bufferValueInMeters, true);
173
                        }
174
                        for (Line hole : splittedHoles) {
175
                            addOffsetedAndBufferedSegment(holes, hole, offsetValueInMeters, bufferValueInMeters, true);
176
                        }
177
                        break;
178

    
179
                    case CONTINUITY_MODE_DISC_CONT:
180
                        //Left
181
                        splittedLine = splitLine(line, data.getSegmentsLength(), data.getHolesLength());
182
                        splittedSegments = splittedLine.getSegments();
183
                        splittedHoles = splittedLine.getHoles();
184
                        for (Line segment : splittedSegments) {
185
                            addOffsetedAndBufferedSegment(segments, segment, -offsetValueInMeters, bufferValueInMeters, true);
186
                        }
187
                        for (Line hole : splittedHoles) {
188
                            addOffsetedAndBufferedSegment(holes, hole, -offsetValueInMeters, bufferValueInMeters, true);
189
                        }
190
                        //Right
191
                        if (line.isClosed() && line.getNumVertices() > 2 && line.isCCW()) {
192
                            Line cloned = line.cloneGeometry();
193
                            cloned.flip();
194
                            addOffsetedAndBufferedSegment(segments, cloned, -offsetValueInMeters, bufferValueInMeters, data.isRoundVertex());
195
                        } else {
196
                            addOffsetedAndBufferedSegment(segments, line, offsetValueInMeters, bufferValueInMeters, data.isRoundVertex());
197
                        }
198
                        break;
199
                        
200
                    case CONTINUITY_MODE_DISC_DISC:
201
                        splittedLine = splitLine(line, data.getSegmentsLength(), data.getHolesLength());
202
                        splittedSegments = splittedLine.getSegments();
203
                        splittedHoles = splittedLine.getHoles();
204
                        //Left
205
                        for (Line segment : splittedSegments) {
206
                            addOffsetedAndBufferedSegment(segments, segment, -offsetValueInMeters, bufferValueInMeters, true);
207
                        }
208
                        for (Line hole : splittedHoles) {
209
                            addOffsetedAndBufferedSegment(holes, hole, -offsetValueInMeters, bufferValueInMeters, true);
210
                        }
211
                        //Right
212
                        for (Line segment : splittedSegments) {
213
                            addOffsetedAndBufferedSegment(segments, segment, offsetValueInMeters, bufferValueInMeters, true);
214
                        }
215
                        for (Line hole : splittedHoles) {
216
                            addOffsetedAndBufferedSegment(holes, hole, offsetValueInMeters, bufferValueInMeters, true);
217
                        }
218
                        break;
219

    
220
                }
221

    
222
            }
223
            data.setSegmentsGeometry(segments);
224
            data.setHolesGeometry(holes);
225
        } catch (Exception ex) {
226
            LOGGER.warn("Can't calculate geometries.", ex);
227
//            Logger.getLogger(DefaultUrbanHorizontalSignageManager.class.getName()).log(Level.SEVERE, null, ex);
228
        }
229

    
230
    }
231

    
232
    protected void addOffsetedAndBufferedSegment(MultiPolygon segments, Line segment, final double offsetValueInMeters, final double bufferValueInMeters, boolean roundVertex) throws GeometryOperationException, GeometryOperationNotSupportedException {
233
        Geometry buffer;
234
        final int joinStyle = roundVertex ? JOIN_STYLE_ROUND : JOIN_STYLE_MITRE;
235
        Geometry segmentOffset = segment.cloneGeometry().offset(
236
                joinStyle,
237
                offsetValueInMeters
238
        );
239
        if(segmentOffset == null){
240
            return;
241
        }
242
        buffer = segmentOffset.buffer(bufferValueInMeters, joinStyle,
243
                true
244
        );
245
        segments.addPrimitives(buffer);
246
    }
247

    
248
    /*
249
        segmentLength & holesLenght in meters
250
    */
251
    /*friend*/SplittedLine splitLine(Line line, double segmentLength, double holesLength) throws CreateGeometryException, GeometryOperationNotSupportedException, GeometryOperationException, CloneNotSupportedException {
252
        GeometryManager geomManager = GeometryLocator.getGeometryManager();
253
        SplittedLine res = new SplittedLine();
254

    
255
        Point previousPoint = null;
256
        double previousLength = 0;
257
        Line currentSegment = geomManager.createLine(line.getGeometryType().getSubType());
258
        boolean isHole = false;
259
        boolean advanceToNext = true;
260
        Iterator<Point> it = line.iterator();
261
        Point currentPoint = null;
262
        while (it.hasNext() || !advanceToNext) {
263
            if (advanceToNext) {
264
                currentPoint = it.next();
265
            }
266
            if (previousPoint == null) {
267
                previousPoint = currentPoint.clone();
268
                currentSegment.addVertex(previousPoint);
269
                advanceToNext = true;
270
                continue;
271
            }
272
            double distance = previousPoint.distance(currentPoint);
273
            if (!isHole) {
274
                if (previousLength + distance < segmentLength) {
275
                    previousLength += distance;
276
                    if(distance > 0.0){
277
                        currentSegment.addVertex(currentPoint);
278
                    }
279
                    previousPoint = currentPoint.cloneGeometry();
280
                    advanceToNext = true;
281
                    continue;
282
                } else {
283
                    //buscar punto dentro del segmento a una distancia = segmentLengthMeters-previousLength
284
                    Point point = calculateIntermediatePoint(previousPoint, currentPoint, (segmentLength - previousLength) / distance);
285
                    //a?adirlo al currentSegment,
286
                    currentSegment.addVertex(point);
287
                    //a?adir  el currentSegment a la lista de segmentos
288
                    res.addSegment(currentSegment.cloneGeometry());
289
                    //crear un nuevo currentSegment y meter el punto como primero
290
                    currentSegment = geomManager.createLine(line.getGeometryType().getSubType());
291
                    currentSegment.addVertex(point);
292
                    //cambiar modo ==> isHole = true
293
                    isHole = !isHole;
294
                    previousPoint = point.clone();
295
                    previousLength = 0;
296
                    advanceToNext = false;
297
                    continue;
298
                }
299
            } else {
300
                if (previousLength + distance < holesLength) {
301
                    previousLength += distance;
302
                    if(distance > 0.0){
303
                        currentSegment.addVertex(currentPoint);
304
                    }
305
                    previousPoint = currentPoint.cloneGeometry();
306
                    advanceToNext = true;
307
                    continue;
308
                } else {
309
                    //buscar punto dentro del segmento a una distancia = segmentLengthMeters-previousLength
310
                    Point point = calculateIntermediatePoint(previousPoint, currentPoint, (holesLength - previousLength) / distance);
311
                    //a?adirlo al currentSegment,
312
                    currentSegment.addVertex(point);
313
                    //a?adir  el surrentSegment a la lista de segmentos
314
                    res.addHole(currentSegment.cloneGeometry());
315
                    //crear un nuevo currentSegment y meter el punto como primero
316
                    currentSegment = geomManager.createLine(line.getGeometryType().getSubType());
317
                    currentSegment.addVertex(point);
318
                    //Cambiar modo Segment <==> Hole
319
                    isHole = !isHole;
320
                    previousPoint = point.clone();
321
                    previousLength = 0;
322
                    advanceToNext = false;
323
                    continue;
324
                }
325
            }
326
        }
327
//        currentSegment.addVertex(currentPoint);
328
        if (currentSegment.getNumVertices() > 1) {
329
            if (isHole) {
330
                res.addHole(currentSegment.cloneGeometry());
331
            } else {
332
                res.addSegment(currentSegment.cloneGeometry());
333
            }
334
        }
335

    
336
        return res;
337
    }
338

    
339
    Point calculateIntermediatePoint(Point p1, Point p2, double lambda) throws CreateGeometryException, GeometryOperationNotSupportedException, GeometryOperationException {
340
        GeometryManager geomManager = GeometryLocator.getGeometryManager();
341
        GeometryType geomType = p1.getGeometryType();
342
        int subtype = geomType.getSubType();
343
        int dimension = geomType.getDimension();
344
        double[] coords = new double[dimension];
345
        Point p = geomManager.createPoint(0, 0, subtype);
346
        double distance = p1.distance(p2);
347
        for (int d = 0; d < dimension; d++) {
348
            p.setCoordinateAt(
349
                    d,
350
                    p1.getCoordinateAt(d) + (p2.getCoordinateAt(d) - p1.getCoordinateAt(d)) * lambda);
351
        }
352
        
353
        return p;
354
    }
355

    
356
    /*friend*/ static class SplittedLine {
357

    
358
        List<Line> segments;
359
        List<Line> holes;
360

    
361
        public SplittedLine() {
362
            segments = new ArrayList<>();
363
            holes = new ArrayList<>();
364
        }
365

    
366
        public List<Line> getSegments() {
367
            return this.segments;
368
        }
369

    
370
        public List<Line> getHoles() {
371
            return this.holes;
372
        }
373

    
374
        public void addSegment(Line segment) {
375
            this.segments.add(segment);
376
        }
377

    
378
        public void addHole(Line hole) {
379
            this.holes.add(hole);
380
        }
381

    
382
    }
383
    
384
    public FeatureStore convertLinesToPolygons(FeatureSet sourceFeatureSet, UrbanHorizontalSignageConfig config, SimpleTaskStatus status) {
385
        
386
        //TODO: el status....
387
                UrbanHorizontalSignageManager uhsManager = UrbanHorizontalSignageLocator.getUrbanHorizontalSignageManager();
388
        EditableFeatureType targetFeatureType = createTargetFeatureType(sourceFeatureSet.getFeatureStore());
389
        FeatureStore targetStore = createTemporalStore(targetFeatureType);
390
        try {
391
            targetStore.edit(FeatureStore.MODE_APPEND);
392

    
393
            for (Feature feature : sourceFeatureSet) {
394

    
395
                Geometry originalGeometry = feature.getDefaultGeometry();
396

    
397
                UrbanHorizontalSignageData data = config.getValues(feature);
398

    
399
                uhsManager.calculateGeometries(originalGeometry, data);
400

    
401
                MultiPolygon multiGeom = data.getSegmentsGeometry();
402
                if (multiGeom != null && multiGeom.getPrimitivesNumber() > 0) {
403
                    for (Geometry geometry : multiGeom) {
404
                        EditableFeature targetFeature = targetStore.createNewFeature();
405
                        targetFeature.copyFrom(feature, (FeatureAttributeDescriptor t) -> !(t.isPrimaryKey() || (t.isIndexed() && !t.allowIndexDuplicateds()) || t.getType() == org.gvsig.fmap.geom.DataTypes.GEOMETRY));
406
                        targetFeature.setDefaultGeometry(geometry);
407
                        targetStore.insert(targetFeature);
408
                    }
409
                }
410

    
411
                switch (data.getContinuity()) {
412
                    case CONTINUITY_MODE_CONT_DISC:
413
                    case CONTINUITY_MODE_DISC_CONT:
414
                    case CONTINUITY_MODE_DISC:
415
                    case CONTINUITY_MODE_DISC_DISC:
416
                        if (data.isPaintHoles()) {
417
                            multiGeom = data.getHolesGeometry();
418
                            if (multiGeom != null && multiGeom.getPrimitivesNumber() > 0) {
419
                                for (Geometry geometry : multiGeom) {
420
                                    EditableFeature targetFeature = targetStore.createNewFeature();
421
                                    targetFeature.copyFrom(feature, (FeatureAttributeDescriptor t) -> !(t.isPrimaryKey() || (t.isIndexed() && !t.allowIndexDuplicateds()) || t.getType() == org.gvsig.fmap.geom.DataTypes.GEOMETRY));
422
                                    targetFeature.setDefaultGeometry(geometry);
423
                                    targetStore.insert(targetFeature);
424
                                }
425
                            }
426
                        }
427
                }
428

    
429
            }
430

    
431
            targetStore.finishEditing();
432
            return targetStore;
433

    
434
        } catch (Exception e) {
435
            LOGGER.debug("Can't create temporary polygons store.", e);
436
        }
437

    
438
        return null;
439
    }
440

    
441
    private EditableFeatureType createTargetFeatureType(FeatureStore store) {
442
        DataManager dataManager = DALLocator.getDataManager();
443

    
444
        EditableFeatureType featureType = dataManager.createFeatureType();
445
        featureType.addAll(store.getDefaultFeatureTypeQuietly());
446

    
447
        EditableFeatureAttributeDescriptor attr = (EditableFeatureAttributeDescriptor) featureType.getDefaultGeometryAttribute();
448
        attr.setGeometryType(Geometry.TYPES.POLYGON, Geometry.SUBTYPES.GEOM2D);
449

    
450
        return featureType;
451
    }
452

    
453
    private FeatureStore createTemporalStore(EditableFeatureType featType) {
454
        if (featType.getStore() != null) {
455
            throw new IllegalArgumentException("Can't create temporal store from a feature type of a already existent store.");
456
        }
457
        try {
458
            // crear ruta de archivo temporal
459
            FoldersManager foldersManager = ToolsLocator.getFoldersManager();
460
            File tempFile = foldersManager.getUniqueTemporaryFile("urbanHorizontalSignage_temporal_store_" + UUID.randomUUID().toString());
461

    
462
            // crear SERVER STORE
463
            DataManager dataManager = DALLocator.getDataManager();
464
            JDBCServerExplorerParameters serverParameters = (JDBCServerExplorerParameters) dataManager.createServerExplorerParameters("H2Spatial");
465
            ((HasAFile) serverParameters).setFile(tempFile);
466
            JDBCServerExplorer serverExplorer = (JDBCServerExplorer) dataManager.openServerExplorer("H2Spatial", serverParameters);
467

    
468
            //Crear tablas en server store
469
            JDBCNewStoreParameters parametersResults = serverExplorer.getAddParameters();
470
            parametersResults.setDynValue("Table", "results");
471

    
472
            parametersResults.setDefaultFeatureType(featType);
473
            serverExplorer.add("H2Spatial", parametersResults, true);
474

    
475
            DataStoreParameters storeParametersResults = dataManager.createStoreParameters("H2Spatial");
476
            storeParametersResults.setDynValue("database_file", tempFile);
477
            storeParametersResults.setDynValue("Table", "results");
478

    
479
            //Creaci?n del store con los resultados
480
            FeatureStore storeResults = (FeatureStore) dataManager.openStore("H2Spatial", storeParametersResults);
481

    
482
            return storeResults;
483
        } catch (Exception ex) {
484
            LOGGER.debug("Can't create temporal store.", ex);
485
            return null;
486
        }
487
    }
488

    
489

    
490

    
491
}