안녕하세요. 씨엔스 입니다.

JPA를 사용하여 Entity 를 만들때 공통적으로 사용되는 컬럼들을 중복해서 사용하지 않고 상속받아 사용할 수 있도록 도와주는 MappedSuperClass 어노테이션에 대해 알아보려고 합니다.

테이블을 생성할 때 공통적으로 사용되는 컬럼들이 존재하고 이를 매번 Entity에 포함하는 경우 코드가 중복되는 경우가 생깁니다.

이때 @MappedSuperClass 를 사용하여 중복되는 코드를 줄이고 공통된 객체를 상속받아 매핑할 수 있습니다.

Board 와 Comment Entity 사이의 공통 속성 객체 BaseSystemFieldEntity 구조

위 이미지는 Board와 Comment라는 두개의 Entity 사이에서 공통적으로 사용되는 속성 객체를 BaseSystemFieldEntity라는 부모로 사용하는 구조를 표현하였습니다.

이를 간단한 소스코드로 표현하자면

 

> BaseSystemFieldEntity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass // @Entity 대신 사용한다.
public class BaseSystemFieldEntity {
    @Column(name = "register_id", updatable = false)
    private String registerId;
    @Column(name = "regist_dt", updatable = false)
    private LocalDateTime registDt;
    @Column(name = "updusr_id")
    private String updusrId;
    @Column(name = "updt_dt")
    private LocalDateTime updtDt;
 
    ...
}
 
cs          

 

> Board

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
public class Board extends BaseSystemFieldEntity {
    @Id
    @Column(name = "bbs_id")
    private String bbsId;
    @Column(name = "bbs_sj")
    private String bbsSj;
    @Column(name = "bbs_cn")
    private String bbsCn;
    @Column(name = "use_at")
    private String useAt;
}
cs

 

> Comment

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
public class Comment extends BaseSystemFieldEntity {
    @Id
    @Column(name = "cmnt_id")
    private String cmntId;
    @Column(name = "bbs_id")
    private String bbsId;
    @Column(name = "cmnt_cn")
    private String cmntCn;
}
cs

 

BaseSystemFieldEntity 클래스는 테이블로 매핑할 필요가 없고 @Entity 어노테이션 대신 @MapperSuperClass 을 사용하면됩니다.

또한 @Entity 어노테이션을 사용한 클래스에 BaseSystemFieldEntity 클래스를 상속받아 사용합니다.

하지만 @MapperSuperClass 이 ORM의 상속 관계와는 달리 자식 클래스의 매핑정보만 제공하는 역할이기 때문에 테이블과는 관련이 없습니다. 즉, 테이블과는 관계가 없다는 단순 매핑 역할일 뿐입니다.(@Entity 사용X) 

그렇기에 직접 생성해서 사용할 일이 없으므로 추상 클래스로 사용하는 것을 권장합니다.

'Language > Java' 카테고리의 다른 글

[Swagger docs]Spring 3.x.x 버전에서 swagger 사용법  (1) 2023.12.27
[Java] Null 체크를 위한 방법  (1) 2022.03.31

안녕하세요. 씨엔스입니다.

 

무려 블로그를 22년 3월에 쓰면서 1년 9개월동안이나 제대로 활용하지 못한 제가 너무나도 민망하네요....

우선 블로그를 시작하고나서 거의 바로 아내의 임신 소식으로 매우 행복한 시간을 보내던 것도 잠시..

거기에 거의 곧바로 아내와 함께 코로나에 감염되어 조심스러운 시간을 보냈었어요.

 

그때 정신이 없더라도 더 열심히 공부하고 블로그 활동도 하며 자기개발을 했어야 했는데, 무언가 너무 늘어지고말았던거같네요.

그렇게 시간을 보내며 22년 11월 건강하게 저의 2세 아들이 태어났습니다 ㅎㅎ

너무너무 기쁘고 행복했던 연말을 보내며 새해를 맞이하고 2월부터 육아휴직에 들어가 아내와 함께 고군분투하며 육아에 전념하던 것도 잠시, 2개월의 짧은 휴직 후 회사로 돌아와 새로운 팀에 들어가게 되었어요.

 

회사에서 구축형 솔루션을 SaaS로 전환하여 B2G, B2C, B2B 에도 도전을 해보자는 말과 함께 새로운 팀에 들어가 기존 프로젝트와는 다르게 FE, BE를 나누어 개발을 해보았습니다.

 

저는 새롭게 들어간 기획팀에서 개발PL을 맡아 프로토타입 개발과 구독형 SaaS 를 진행하면서 필요한 화면과 DB를 설계하고 FE, BE 구분없이 개발을 진행했습니다.

 

FE와 BE를 나누어 개발할 수 있는 줄 알았는데 저는 해당이 안되더라구요..^^ 결국 사람이 부족하기 때문이지요 ㅠㅠ

 

하지만 이렇게 나누어진 상황에서 FE에서는 Vue js를 사용하여 컴포넌트 단위의 화면을 구성하고 라우터에 대한 개념도 새롭게 알게되며 공부할 수 있던 점이 저에게는 많은 도움이 되었고, BE에서 JPA를 활용한 RESTfull API 개발과 mybtis를 대신하여 QueryDSL을 사용한 점도 도움이 되었습니다.

 

11월에 맞춰 서비스를 오픈해야 하기에 짧은 시간동안 많은 시행착오와 추가 요구사항에 대한 방어 및 수용 후 개발을 반복하며 겨우겨우 시간에 맞춰 서비스를 성공적으로 오픈하였네요.

 

팀원들은 밤새고 저는 퇴근 후 육출을 하다가 육퇴후 밤에 다시 일을하고 새벽에 출근하고를 무한반복......했던거같네요ㅠㅠ 고생한 팀원들 정말 감사합니다 :)

 

2023년을 돌이켜보니 육아와 서비스 오픈을 위한 일 말고는 정말 아무것도 못했던 것 같으면서도 개발하면서 막혔던 부분이나 오류, 고생했던 부분을 정리하여 짧게라도 블로그에 올려 많은 분들과 공유했으면 어땟을까 하는 아쉬움이 참 많이 남는 것 같네요.

 

이번을 기점으로 저도 다시금 코드 개선 및 추가로 개발해야하는 부분에서 이슈나 공유하고자 하는 내용이 있는 경우 나태하지않고 꾸준히 글을 작성해볼까 합니다!

 

회고록이 아닌 회고록처럼 되어버린...주요 내용이 없지만 그래도 22~23년을 되돌아보는 시간이였고, 앞으로 더 노력해보겠습니다. 

 

이 글을 읽는 모든 분들 새해 복 많이 받으시고 건강하세요 :)

 

 

'주저리주저리' 카테고리의 다른 글

블로그를 시작하면서...  (0) 2022.01.05

안녕하세요. 오랜만에 인사드립니다.

 

개인적으로 많은 일들이 있던 2023년이라서 너무나도 오랫동안 블로그를 못쓰고 있었네요. 자세한 이야기는 따로 썰을 풀어서 글을 써볼까 합니다 ㅎㅎ

 

일단 이 글의 목적은 개발을 진행하면서 API 문서로 swagger를 많이 사용하고 있는데, Spring 3버전 이상부터는 기존에 사용하던 springfox 가 아닌 springdoc-openapi-ui 라이브러리를 사용해야 한다는 내용을 소개하려고 합니다!

 

"swagger 사용법" 이라고 검색하게 되면 대부분이 springfox를 이용한 swagger 사용법들이 나오고 있어 저도 그렇게 사용하는줄 알았는데, 이제는 springdoc을 사용하여 swagger를 써야한다는 걸 알게되었는데요.

 

이유는 springfox는 2020년 7월 14일에 3.0.0 버전을 마지막으로 더이상 업데이트가 되지 않는다는 것을 알 수 있습니다.

(참고: https://github.com/springfox/springfox)

 

그에 비해 springdoc-openapi 의 경우 제가 작성하는 날짜를 기준(23년12월27일)으로 3주전까지 업데이트가 되고 있습니다.

(참고: https://github.com/springdoc/springdoc-openapi)

그러기에 Spring 버전이 올라가더라도 적용이 가능한 springdoc-openapi 사용이 필수적이라고 보여집니다.

 

간단한 사용법을 설명드리자면 먼저 해당 라이브러리를 적용해야합니다.

 

Gradle / Maven 에 dependency 를 추가하시면 됩니다.

공식 springdoc Github 에서 공식 문서로 제공해주고 있는데

 

- Gradle

## implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'

 

- Maven

<dependency>

     <groupId>org.springdoc</groupId>

     <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>

     <version>2.3.0</version>

</dependency>

 

으로 추가하여 사용하실 수 있습니다.

 

그리고 SwaggerConfig.java 라는 파일을 생성하여 설정 할 수도 있습니다.

 

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {
    @Bean
    public OpenAPI openAPI(){
        Info info = new Info()
                .title("Spring Boot 를 이용한 API 확인 Swagger")
                .version("0.1")
                .description("API 문서입니다. \n Demo 버전으로 들고다니는 용도입니다.");
        return new OpenAPI()
                .components(new Components())
                .info(info);
    }
}

 

위와 같은 형식으로 파일을 생성하여 사용하시면 

url에 http://localhost:사용자port/swagger-ui/index.html 를 입력하여 들어가면 

 

 

이런 화면이 나타나며 swagger를 사용하실 수 있습니다.

 

2버전과는 조금 달라진 ui가 있지만 크게 차이가 없기 때문에 사용하시는데 어려움은 없으실테고,

Spring 3 버전 이후 부터는 springfox가 아닌 springdoc-openapi 라이브러리를 사용하시는걸 잊지 마시고 헷갈리지 않으셨으면 합니다.

 

감사합니다 :)

안녕하세요. 씨엔스입니다.

 

우리가 개발을 할 때 가장 많이 발생하는 예외 처리 중 하나가 바로 NullPointerException 이라 생각합니다.

Null 오류를 발생시키지 않기 위해서 지금까지는 분기처리를 통해 Null 체크 여부를 확인했는데,

자바 8 버전에서 나온 Optional 클래스를 통해 NPE 방지를 알아보도록 하겠습니다.

 

 

  • Java 8 버전 이전 null 예외처리 방식
1
2
3
4
5
6
7
8
9
10
11
class CodeRunner{
    public static void main(String[] args){
        List<String> names = null;
        List<String> list = getNames();         
        // null 인 경우
        if(list != null) {
            // next step
        }
    }
}
 
cs

ㅇㅇㅇㅇㅇ

기존에는 null이 발생하는 상황이 생길 경우 if문을 이용하여 null을 체크 하지만, 이와 같은 경우 코드의 가독성이 떨어지고 해당 객체가 null을 가질 수 있는 객체인지, 필수 값인지 직관적으로 알 수 있는 방법이 없어 오류를 찾더라도 해결하기에 어려움이 발생할 수 있었습니다.

 

  • Java 8 버전에 나온 Optional을 사용한 방식
1
2
3
4
5
6
7
8
9
10
class CodeRunner{
    public static void main(String[] args){
        List<String> list = getNames();
        // list 가 null 인지 아닌지 체크
        Optional <List<String>> optional = Optional.ofNullable(list);
        System.out.println(optional); // Optional.empty 
        System.out.println(optional.isPresent()); // false    
    }
}
 
cs

 

Java 8 버전에 처음 도입된 Optional은 값이 존재할 수도 있고, 아닐 수도 있는 값을 객체로 포장해주는 클래스입니다.

null 체크를 직접 할 필요가 없고 NPE가 발생할 가능성이 있는 값을 다룰 필요가 없다는 장점이 있습니다.

 

 

Optional 사용하기

 

  • Optional.empty() : 비어있는 Optional 객체 생성하기(return : Optional.empty)
1
Optional<String> opt = Optional.empty(); // 비어있는 Optional 객체를 생성
cs

 

  • Optional.of() : Null 값이 없는 Optional 객체 생성하기(값이 null 이면 NPE 발생)
1
Optional<String> opt = Optional.of("value 존재"); // 값이 존재하는 Optional 객체 생성(Optional[value 존재])
cs

 

  • Optional.ofNullable() : null 이거나 값이 있을 수 있는 객체를 생성한다. (null 여부를 확신할 수 없을 때)
1
2
3
// Optional의 value는 값이 있을 수도 있고 null 일 수도 있다. 
Optional<String> optional1 = Optional.ofNullable(null); 
Optional<String> optional2 = Optional.ofNullable("value 존재");
cs

 

 

Optional 값 체크하기

 

  • get() : Optional의 값을 가져오고, 비어있는 Optional 객체에 대해서는 NoSuchElementException 오류가 발생한다.
1
2
Optional<String> opt = Optional.of("value 존재");  // Optional 값이 존재
System.out.println(opt.get()); // return value 존재
cs

 

  • orElse() : 비어있는 Optional 객체에 대하여 orElse로 넘어온 값을 반환한다.
1
2
Optional<String> opt1 = Optional.empty(); // 빈 Optional 객체
System.out.println(opt1.orElse("value")); // return value
cs

 

  • orElseGet() : 비어있는 Optional 객체에 대하여 orElseGet로 넘어온 함수형 값을 통해 생성된 객체를 전달한다.
1
2
Optional<String> opt2 = Optional.empty(); // 빈 Optional 객체
System.out.println(opt2.orElseGet(() -> new String("새로운 값"))); // return value
cs

 

  • orElseThrow() : 비어있는 Optional 객체에 대하여 orElseThrow로 넘어온 함수형 값을 통해 오류를 발생시킨다.
1
2
Optional<String> opt3 = Optional.empty(); // 빈 Optional 객체
System.out.println(opt3.orElseThrow(RuntimeException::new)); // return value
cs

 

  • map() : Optional 객체의 값을 원하는 형태로 형변환을 할 수 있다.
1
2
Optional<String> opt = Optional.of("TEST"); // Optional 객체 값 존재할 경우
System.out.println(opt.map(String::length)); // Optional 객체의 형태를 변경
cs

 

  • filter() : filter 메소드의 인자로 받은 람다 식이 참이면 Optional 객체를 그대로 통과시키고 거짓이면 Optional.empty()를 반환해서 추가로 처리가 안되도록 한다.
1
2
Optional<String> opt = Optional.of("test");
System.out.println(opt.filter(v -> v.equals("TEST"))); // true : Optional[TEST] , false : Optional.empty
cs

 

 

정리

 

  1. Optional 객체를 사용할 경우 null을 선언할 필요가 없습니다.
  2. 반환하는 경우에만 사용하자.
  3. 상황에 맞게 사용해야한다. (남발하지말라, 오히려 독이 된다.)

 

아직 실무에서 적용해본 적이 없어 저도 미숙하기에 이렇게 정리를 하면서 공부해보았는데, 실제 소스에서의 null 처리를 Optional을 통해 진행해볼까 합니다.

감사합니다.

 

 

-참고자료

 

 

1편에 이어서 2편에서는 Image Layer 를 배경지도 위에 표현해보도록 하겠습니다.

 

Image Layer

 

Image Layer를 생성하기 위해 저는 GeoServer에 택지정보시스템에서 제공하는 지구정보데이터를 레이어로 발행시켜두고 진행하였습니다 :)

 

GeoServer를 사용하는 법이 궁금하신 분들은 GeoServer 공식사이트에서 제공하는 메뉴얼을 참고하시면 쉽게 이해하실 수 있습니다. 영어지만 이미지가 모두 첨부되어 있습니다! 본인이 사용하는 버전에 맞는 메뉴얼을 찾아보세요.

 

https://docs.geoserver.org/stable/en/user/index.html

 

GeoServer User Manual — GeoServer 2.20.x User Manual

GeoServer 2.20.x User Manual GeoServer User Manual GeoServer is an open source software server written in Java that allows users to share and edit geospatial data. Designed for interoperability, it publishes data from any major spatial data source using op

docs.geoserver.org

 

Image Layer 를 생성하기 위해 배경지도 맵을 생성합니다.

지난 Vector Layer 와 동일하게 Map 객체를 Layer 와 View 객체를 나누어 선언하여도 되고 함께 사용하셔도 됩니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 배경지도 레이어
let layers = [
    new ol.layer.Tile({
        source: new ol.source.OSM()
    })
];
 
// 배경지도의 View 객체
let view = new ol.View({
    center: [127.8041858889821835.88559485525495],
    projection : 'EPSG:4326',
    zoom: 7
});
 
// 배경지도가 있는 Map
const map = new ol.Map({
    layers: layers,
    target: 'map',
    id : 'map',
    view: view
});
cs

 

배경지도 맵을 생성한 뒤 이제 Image Layer를 생성할텐데, 그 전에 Openlayers에서 Image Layer 를 사용할 수 있는 Source가 다양하게 있습니다. 

  • ol.source.ImageArcGISRest
  • ol.source.ImageCanvas
  • ol.source.ImageMapGuide
  • ol.source.ImageStatic
  • ol.source.ImageWMS
  • ol.source.Raster

저는 이 중에서 ol.source.ImageWMS 를 사용했습니다. 가장 기본적으로 사용되는 WMS source 이고 GeoServer 에 발행한 레이어를 호출할 수 있습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
let wmsSource = new ol.source.ImageWMS({
    url : 'http://192.168.0.50:9001/geoserver/wms'// GeoServer를 설치한 서버의 URL 과 port를 사용합니다.
    params : {'LAYERS' : 'Sample:the_geom'}, // 발행했던 레이어의 이름을 사용합니다. 작업공간이 여러개면서 같은 레이어를 사용할 경우 작업공간 이름까지 선언해주면 더 명확합니다.
// url : 'http://192.168.0.50:9001/geoserver/Sample/wms?service=WMS&version=1.1.0&request=GetMap&layers=Sample:the_geom', // 혹은 url에 service, request, layers, version 등의 요소를 추가하여 사용할 수 도 있습니다.
    ratio : 1,
    serverType : 'geoserver'
});
 
let wmsLayer = new ol.layer.Image({
    source : wmsSource
});
 
map.addLayer(wmsLayer);
cs

어떻게 보면 Vector Layer 보다 쉽게 레이어를 호출합니다 :)

 

Image Layer 생성 화면

 

GeoServer에 발행한 레이어를 배경지도에 추가하여 표출하면 이렇게 나타난다!

이렇게 호출된 Image Layer 에 각 속성정보를 알고 싶을 때는 Openlayers Source의 함수 중 getGetFeatureInfoUrl() 을 사용하여 확인할 수 있습니다.

 

Openlayers에서 제공하는 API 설명

getGetFeatureInfoUrl() 은 좌표, 해상도, 좌표계, 데이터를 읽을 포맷 으로 파라미터를 주면 사용자가 읽어드릴 포맷 형식으로 데이터를 제공받을 수 있습니다.

 

1
2
3
4
5
6
7
8
9
10
map.on('click'function (evt) {
    document.getElementById('info').innerHTML = ''// 속성정보를 표출할 div 초기화
 
    let viewResolution = view.getResolution(); // 
    let url = wmsSource.getGetFeatureInfoUrl(evt.coordinate, viewResolution, 'EPSG:4326', { 'INFO_FORMAT''text/html' }); // 좌표, 해상도, 좌표계, 읽을 포맷
 
    if(url){
        document.getElementById('info').innerHTML = '<iframe width="100%" seamless="" src="' + url + '"></iframe>';
    }
});
cs

 

위와같이 작성하게 되면 지도에 feature 를 클릭하면 info div에 속성정보가 나타나게 됩니다.

 

Image Layer feature 클릭 이벤트 화면

 

역시 캡쳐화면보단 움직이는게 눈에 확 들어오네요!

Vector Layer에 이어 Image Layer 를 지도화면에 표출하는 방법에 대하여 설명을 해보았습니다.

가장 기본적인 Openlayers 기능이면서 레이어를 표출할 때 쓰기에 정리를 해봤습니다.

Image Layer 의 전체 소스도 첨부합니다!

wmsLayers.html
0.00MB

 

앞으로는 레이어를 좀 더 동적으로 만드는 방법이라거나 스타일을 동적으로 변경하는 등, 더 다양한 OpenLayers 기능을 소개해보도록 하겠습니다. 감사합니다 :)

'GIS > Openlayers' 카테고리의 다른 글

[Openlayers] Vector Layer 와 Image Layer - 1  (0) 2022.01.07
[Openlayers] GIS 오픈소스 Openlayers란?  (2) 2022.01.06

안녕하세요. 씨엔스 입니다 :)

 

오늘은 GeoServer에 대하여 간단하게 알아보고자 합니다.

GeoServer 는 지리공간 데이터를 공유하고 편집할 수 있는 Java 로 개발된 오픈 소스 GIS 소프트웨어 서버입니다. 상호운용성을 전제로 개발되었기 때문에, 개방형 표준을 사용하여 다양한 공간 데이터 소스를 서비스할 수 있게 합니다.

 

http://geoserver.org/

 

GeoServer

GeoServer is an open source server for sharing geospatial data. Designed for interoperability, it publishes data from any major spatial data source using open standards. Special Thanks The following products are kindly supporting open source projects like

geoserver.org

 

GeoServer가 제공하는 웹 서비스를 간단하게 그려봤습니다 :)

위에 그림처럼 GeoServer 가 제공하는 서비스는 다양합니다.

각각의 서비스가 어떤 역할을 하고 어떻게 레이어로 제공하는지 알아보겠습니다.

 

WMS(Web Map Service)

  • GIS(지리 정보 시스템) 데이터베이스에서 데이터를 사용하기 위해 맵 서버에서 생성된 지도 이미지를 인터넷상에서 제공하기 위한 표준 프로토콜
  • WMS 요청은 관심 영역과 지리적 레이어를 처리하는것으로 정의
  • WMS 요청에 대한 응답은 이미지(JPEG, PNG 등)로 제공
  • 데이터서버에 저장된 레이어 또는 분석을 통해 생성된 벡터 및 래스터 데이터를 시각화(Visualiziont)하는 서비스
  • Open Geospatial Consortium (OGC)에 의해 개발되어 최초로 공개

 

WFS(Web Feature Service)

  • 웹에서 벡터형식으로 지리정보를 생성,수정,교환되는 방식의 인터페이스 표준
  • Http로 요청하고 XML, GeoJson등으로 받음
  • DB에 저장된 레이어 또는 분석을 통해 생성된 백터,레스터 데이터를 가지고옴
  • 사용자가 편집할 수 없는 WMS와 달리 피쳐 자체로 접근하여 편집 가능

 

WCS(Web Coverage Service)

  • 정적 지도(서버에서 그림으로 렌더링)를 반환하기 위해 공간 데이터를 묘사하는 WMS와 달리 WCS는 자세한 설명과 함께 사용 가능한 데이터를 제공
  • 개별 지리 공간 기능을 반환하는 WFS와 달리 WCS는 시공간 도메인을 속성 범위(다차원적일 수 있음)와 관련시키는 시공간 변화 현상을 나타내는 커버리지를 반환
  • GML , GeoTIFF , HDF-EOS , CF-netCDF 또는 NITF 와 같은 다양한 데이터 형식으로 적용 범위를 전달

OGC에서 제공하는 WCS 모델

WPS(Web Processing Service)

  • 지리자료의 처리, 알고리즘, 계산 등을 수행하기 위한 OGC 서비스
  • 지리정보들에 대한 다양한 처리 서비스(Geo-Processing이란 GIS 데이터를 조작하기 위해 사용되는 작업으로 하나 이상의 입력데이터를 이용하여 자료 처리 후 결과물을 반환하는 형식이며, 일반적으로 중첩, 래스터 분석, 데이터 변환등이 있음 service)들을 웹 상에서 정의하고 접근할 수 있도록 하기 위한 인터페이스이며 모든 OGC 표준 웹 서비스들과 상호호환성을 갖도록 정의
  • 특정 데이터에 직접 바인딩되어 있지 않으며, 클라이언트에 의해 동적으로 주어지는 데이터 또는 데이터 참조(WFS 결과물 등)들을 입력으로 받아들여 이를 처리하는 프로세스 서비스들로 구성
  • 간단한 계산(버퍼 연산 등)에서부터 복잡한 분석 연산(기후 모델의 생성 등)을 지원하며, 원칙적으로 WPS인터페이스를 기반으로 구현함에있어 어떠한 제약사항도 없음

 

- 한줄 요약

  • WMS : 배경지도나 주제도의 시각화 및 범례
  • WFS : 벡터 데이터의 공간 및 속성 조회와 편집
  • WCS : 래스터 데이터의 추출(Subset, Resampling, Reprojection)
  • WPS : 공간분석 및 처리

 

이러한 GeoServer를 어떻게 사용하는지는 다음 글에서 설명해보도록 하겠습니다. :)

안녕하세요. 씨엔스입니다.

Openlayers 에서는 레이어를 생성하여 지도화면에 표출할 때 

Vector Layer 와 Image Layer 를 많이 사용합니다. (물론 Tile Layer도 사용합니다.)

지난 글에서는 간단하게 배경지도를 생성하는 것을 설명 드렸는데, 이번엔 Vector 와 Image 레이어를 생성하여 배경지도 위에 표현해보려고 합니다.

간단하지만 Openlayers의 구조와 배경지도 생성하는 방법이 궁금하신 분은 제 이전 글은 참조하시면 됩니다 :)

 

https://clsung.tistory.com/5

 

[Openlayers] GIS 오픈소스 Openlayers란?

안녕하세요. 씨엔스 입니다. 오늘은 GIS 개발에서 오픈소스로 사용되고 있는 Openlayers 에 대하여 간략하게 소개하고 설명해보려 합니다. 지도서비스를 개발하기 위해서 Kakao나 Naver의 지도 API를 사

clsung.tistory.com

 

Vector Layer

Vector 레이어를 생성하기 위해서는 우선 내가 생성하고자 하는 레이어의 데이터가 필요합니다.

저는 택지정보시스템에서 제공하는 지구정보데이터 중 하나의 데이터를 커스터마이징하여 사용하였습니다.

 

먼저 Vector Layer 를 생성하기 위해 배경지도 맵을 생성합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 배경지도 레이어
let layers = [
    new ol.layer.Tile({
        source: new ol.source.OSM()
    })
];
// 배경지도의 해상도, 위치, 레벨 설정
let view = new ol.View({
    center: [127.1036920655848537.462755923721225],
    projection : 'EPSG:4326',
    zoom: 15
});
// WFS Layer(Vector) 가 있는 Map
const map = new ol.Map({
    layers: layers,
    id : 'map',
    target: 'map',
    view: view
});
cs

위와 같이 각각의 객체를 나누어 사용하여도 되고,

 

1
2
3
4
5
6
7
8
9
10
11
12
const map = new ol.Map({
    layers: [new ol.layer.Tile({
        source: new ol.source.OSM()
    })],
    id : 'map',
    target: 'map',
    view: new ol.View({
        center: [127.1036920655848537.462755923721225],
        projection : 'EPSG:4326',
        zoom: 15
    })
});
cs

한번에 선언하여 맵을 사용할 수 있습니다.

 

 

이렇게 배경지도를 생성하고 난 뒤 Vector Layer를 생성할 차레입니다.

Vector Layer는 Openlayers에서 제공하는 포맷 중 하나인 GeoJSON 형식으로 가지고 왔습니다.

 

1
2
//WFS데이터
const geoJson={"type":"Feature","properties":{"zoneCode":"11680DA2005001","zoneName":"서울세곡국민임대주택단지예정지구","stepCode":"PC"},"geometry":{"type":"Polygon","coordinates":[[[127.1063733,37.4640793,0.0],[127.1063833,37.4641394,0.0],[127.1064778,37.464681,0.0],[127.1047184,37.464804,0.0],[127.1044987,37.4648193,0.0],[127.1037383,37.4648909,0.0],[127.1029872,37.4649399,0.0],[127.1026356,37.4649628,0.0],[127.10166,37.4650265,0.0],[127.1006197,37.465108,0.0],[127.1005497,37.4651136,0.0],[127.1005075,37.4651169,0.0],[127.1004826,37.4651194,0.0],[127.0996025,37.4652087,0.0],[127.0995163,37.4652174,0.0],[127.0995566,37.4651815,0.0],[127.0995902,37.4650503,0.0],[127.0996391,37.4649353,0.0],[127.0996492,37.4648481,0.0],[127.0996378,37.4647666,0.0],[127.0995942,37.4645815,0.0],[127.0997194,37.4643419,0.0],[127.0997588,37.4642833,0.0],[127.0998869,37.4640891,0.0],[127.0999187,37.4640408,0.0],[127.099968,37.4639661,0.0],[127.100238,37.4637608,0.0],[127.1002862,37.4635808,0.0],[127.100413,37.4632244,0.0],[127.100717,37.4628659,0.0],[127.1010823,37.4625112,0.0],[127.1011359,37.4623895,0.0],[127.1011282,37.4623793,0.0],[127.1011405,37.4623342,0.0],[127.10111,37.4623069,0.0],[127.101091,37.4622772,0.0],[127.1010568,37.4622081,0.0],[127.1010621,37.4621719,0.0],[127.1010357,37.4621446,0.0],[127.1010185,37.4621271,0.0],[127.1009859,37.4621228,0.0],[127.1009717,37.4621031,0.0],[127.1009725,37.4620688,0.0],[127.101006,37.4620237,0.0],[127.1010133,37.4619893,0.0],[127.101021,37.4619745,0.0],[127.1010378,37.4619419,0.0],[127.1010406,37.461939,0.0],[127.1010821,37.4618966,0.0],[127.1011887,37.4618516,0.0],[127.1012469,37.4618361,0.0],[127.1012522,37.4618358,0.0],[127.1013267,37.4618279,0.0],[127.1013299,37.4617598,0.0],[127.1013435,37.4617261,0.0],[127.1013516,37.4616894,0.0],[127.1013678,37.4616447,0.0],[127.1013689,37.4615936,0.0],[127.1013776,37.4615446,0.0],[127.1014264,37.4614878,0.0],[127.1013141,37.4613057,0.0],[127.1012766,37.461283,0.0],[127.1012668,37.4612785,0.0],[127.1011796,37.4612724,0.0],[127.1010823,37.4612335,0.0],[127.1008972,37.4611763,0.0],[127.1007006,37.4611592,0.0],[127.1004587,37.4611275,0.0],[127.0995616,37.460774,0.0],[127.0993922,37.4605753,0.0],[127.0993402,37.460487,0.0],[127.0993505,37.4602831,0.0],[127.0994026,37.4601428,0.0],[127.0995651,37.4599418,0.0],[127.0996856,37.4598223,0.0],[127.0997964,37.4597234,0.0],[127.0999333,37.4596142,0.0],[127.100106,37.4595595,0.0],[127.1003036,37.4595856,0.0],[127.1003149,37.4595794,0.0],[127.1003386,37.4595663,0.0],[127.1010049,37.4591995,0.0],[127.1013932,37.4589725,0.0],[127.1016008,37.4590387,0.0],[127.1018291,37.4591827,0.0],[127.1019657,37.4593498,0.0],[127.1021106,37.4594236,0.0],[127.1021445,37.4594393,0.0],[127.1022708,37.459521,0.0],[127.1022995,37.4595387,0.0],[127.102646,37.4598674,0.0],[127.1026827,37.4597781,0.0],[127.1027003,37.4597329,0.0],[127.1028087,37.4597254,0.0],[127.1029542,37.4597325,0.0],[127.103073,37.4597611,0.0],[127.1031674,37.459766,0.0],[127.1032543,37.4597701,0.0],[127.1034514,37.4597574,0.0],[127.1035588,37.4598353,0.0],[127.1036495,37.4598677,0.0],[127.1038207,37.459932,0.0],[127.1038999,37.4600065,0.0],[127.1039547,37.4600568,0.0],[127.1039812,37.4601836,0.0],[127.1039864,37.4602315,0.0],[127.1039861,37.4602688,0.0],[127.1039978,37.4602688,0.0],[127.1039919,37.4604538,0.0],[127.1040353,37.4605576,0.0],[127.1040582,37.4605856,0.0],[127.1041827,37.4607269,0.0],[127.1042611,37.460802,0.0],[127.1043109,37.4608172,0.0],[127.1044202,37.4609565,0.0],[127.1044388,37.460985,0.0],[127.1044841,37.4610294,0.0],[127.1045325,37.4610566,0.0],[127.1046371,37.4610998,0.0],[127.1047922,37.46116,0.0],[127.1047616,37.4612636,0.0],[127.1046237,37.4614448,0.0],[127.1045623,37.4616124,0.0],[127.1045229,37.4616837,0.0],[127.1044829,37.4617559,0.0],[127.104477,37.4617746,0.0],[127.104353,37.4621748,0.0],[127.104717,37.4621546,0.0],[127.1048787,37.4621611,0.0],[127.1049343,37.4621545,0.0],[127.1050422,37.4621417,0.0],[127.1051403,37.4621462,0.0],[127.1052828,37.4621705,0.0],[127.10562,37.4621785,0.0],[127.105668,37.4622361,0.0],[127.1059373,37.4623546,0.0],[127.1061539,37.4624013,0.0],[127.1061791,37.4624068,0.0],[127.1063594,37.4624174,0.0],[127.106479,37.4624244,0.0],[127.1066839,37.462411,0.0],[127.1068918,37.46237,0.0],[127.1071967,37.4622332,0.0],[127.1073135,37.4621808,0.0],[127.1074382,37.4621858,0.0],[127.1075262,37.4621894,0.0],[127.1077909,37.4622245,0.0],[127.1078208,37.4624344,0.0],[127.1078376,37.4624651,0.0],[127.1076816,37.4625014,0.0],[127.1074559,37.4625457,0.0],[127.1074139,37.4625539,0.0],[127.1074334,37.4626746,0.0],[127.1074223,37.4626756,0.0],[127.107347,37.4626816,0.0],[127.1071586,37.4626968,0.0],[127.1069913,37.4627112,0.0],[127.1069498,37.4627147,0.0],[127.1067477,37.4627298,0.0],[127.1067228,37.4627336,0.0],[127.1066783,37.4627404,0.0],[127.1066767,37.4627406,0.0],[127.1064691,37.4627552,0.0],[127.1064221,37.4627602,0.0],[127.1062607,37.4627751,0.0],[127.1061922,37.4627809,0.0],[127.1060057,37.4627942,0.0],[127.1059456,37.462799,0.0],[127.105826,37.4628083,0.0],[127.1058291,37.4628309,0.0],[127.1058298,37.4628366,0.0],[127.1058516,37.4629966,0.0],[127.1058023,37.4630001,0.0],[127.1054841,37.4630227,0.0],[127.1054475,37.4630262,0.0],[127.1053363,37.4630338,0.0],[127.1053006,37.4630167,0.0],[127.1052331,37.4630225,0.0],[127.1053323,37.4637225,0.0],[127.1053377,37.46376,0.0],[127.1053425,37.4637941,0.0],[127.1053602,37.4637983,0.0],[127.1053776,37.4638025,0.0],[127.1053942,37.463971,0.0],[127.1054661,37.4639647,0.0],[127.1054919,37.4639616,0.0],[127.1055527,37.4639557,0.0],[127.1057135,37.463944,0.0],[127.1058477,37.4639327,0.0],[127.1061737,37.4639029,0.0],[127.1063419,37.4638908,0.0],[127.1063486,37.4639312,0.0],[127.1063733,37.4640793,0.0]]]}};
cs

이 데이터를 이제 Vector Layer 로 생성하는데, 이때 Openlayers의 ol.format.GeoJSON() 을 사용합니다.

 

1
2
3
4
5
6
// geojson이 담길 vector layer의 feature(단위 집합들)
let features = new ol.format.GeoJSON().readFeatures(geoJson);
// feature 들을 담을 vector source
let vectorSource = new ol.source.Vector({
    features : features
});
cs

이렇게 feature를 source에 담아 생성한 뒤 레이어를 생성하면 Vector Layer가 생성되고 이를 map 객체에 addLayer 함수를 이용해 추가해주면 지도화면에 데이터가 지도로 표출됩니다.

 

1
2
3
4
5
6
// vector layer
let vectorLayer = new ol.layer.Vector({
    source : vectorSource
});
 
map.addLayer(vectorLayer); // vector layer를 Map에 추가
cs

 

Vector Layer 생성 화면

 

Vector Layer 생성 성공! 근데... 잘 안보인다....

 

위와 같은 화면이 나타나면 정상적으로 Vector Layer를 생성한 것입니다.
근데 아무리봐도 제대로 나온건 알겠는데 눈으로 잘 안보이는 상황이 보이네요.
Openlayers 에서 Vector Layer의 Default 색상을 저렇게 표현하고 있더라구요 :(
그래서 이참에 한번 확실하게 눈에 보이도록 하기 위해 Style에 대해서 같이 설명해 보려고 합니다 :)

 

이정도면 눈에 잘 보이는거 같군!!

Vector Layer에 스타일을 적용하게 되면 이렇게 눈에 확 띄는 색상이라던지 내가 원하는 다양한 색을 입혀볼 수 있습니다.

 

스타일을 적용하기 위해서는 Style 객체를 사용해야 하는데요.

 

1
2
3
4
5
6
// 벡터 레이어의 스타일 색
let style = new ol.style.Style({
    fill : new ol.style.Fill({color : 'rgba(215, 35, 77, 1)'}), // rgb 코드 사용 가능
    // fill : new ol.style.Fill({color : '#d7234d'}), // HEX 코드 사용 가능
    stroke : new ol.style.Stroke({color: 'rgba(34, 160, 235, 1)'})
});
cs

이렇게 스타일 객체를 생성할 수 있습니다. 

내가 원하는 색을 넣은 스타일 객체를 이제 Vector Layer 에 적용해주면 되는데, 위에서 언급했듯이 Vector Layer 는 Vector Source 를 가지고 있고 그 안에 Feature 들이 집합으로 있기 때문에 Feature 에다가 스타일을 적용해야 합니다.

 

1
2
// feature에 스타일 적용(feature는 배열 구조로 수많은 feature가 있을 수 있다)
for(var i in features) features[i].setStyle(style);
cs

이렇게 해주면 내가 원하는 색상도 스타일 객체를 통해 Vector Layer에 담게 되었습니다.

 

그런데 가만 생각해보면 Vector Layer에 담겨져 있는 데이터 내용이 궁금하고 저 Layer를 생성하기 위해 우리는 GeoJSON을 이용하였는데, 그저 공간정보 데이터를 가지고 나타내기만 할 수 있을까요?

생성한 Vector Layer를 클릭하여 원하는 정보를 보고싶을 수 있으니깐요!

위에 선언한 geoJson 에는 여러 데이터가 존재하는데, 그 중에서 key 가 "zoneName" 인 데이터를 화면에 나타내보도록 하겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
// feature 를 클릭할 수 있는 select
let selectSingleClick = new ol.interaction.Select({
    condition: ol.events.condition.click, // click 하는 이벤트 (pointMove, singleclick, altClick 등)
    style: new ol.style.Style({
        stroke: new ol.style.Stroke({
            color: 'white',
            width: 2
        }),
        fill: new ol.style.Fill({
            color: 'rgba(0,0,255,0.6)'
        })
    })
});
cs

Vector Layer 의 feature 를 클릭하여 선택하는 Select 객체를 생성합니다. 

- condition : Select 객체 사용시 click, move 등의 이벤트를 설정

- style : Select 객체 사용시 색상 변경

 

1
2
// map 객체에 add
map.addInteraction(selectSingleClick);
cs

Map 객체에 Select 객체를 추가해줍니다.

그리고나서 마지막으로 생성한 Vector Layer의 feature 를 클릭할 경우의 이벤트 함수를 작성해줍니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 기존 색상을 담는 객체
let _style = null;
// feature 를 선택할 때 이벤트
selectSingleClick.on('select'function(e) {
    if(e.target.getFeatures().a[0!= undefined){
        _style = e.target.getFeatures().a[0]; // 기존 색상 담기
        e.target.getFeatures().a[0].setStyle(null); // 기존 색상 제거
    }
    if(e.target.getFeatures().a.length > 0){
        document.getElementById('info').innerHTML = e.target.getFeatures().a[0].N.zoneName;
    }else{
        _style.setStyle(style); // 기존 색상 추가
        document.getElementById('info').innerHTML = "";
    }
});
cs

 

Vector Layer feature 클릭 이벤트 화면

 

마우스 클릭 이벤트를 통해 feature click 과 feature data 표출

이렇게 색상을 변경함으로써 어떤 feature 를 선택했는지와 선택한 feature 의 data도 불러온 것을 확인할 수 있습니다.

 

캡쳐화면은 영 맘에 안들어..... 이게 훨씬 낫네요 :)

 

gif 파일로 올려서 보니 아무래도 눈에 확 와닿는 기분은 저만 느낄 수도 있지만 그래도 충분히 이해하실거라 생각됩니다.

Vector Layer 를 지도화면에 표출하는 방법에 대하여 설명을 해보았습니다.

많이 부족하기 때문에 여전히 정신없고 기본 중에 기본을 작성중이지만, GIS 개발을 처음하시는 분들이나 Openlayers를 처음 접하는 분들이 이 글을 읽고 쉽게 접근하셨으면 하는 바람이 큽니다.

 

Vector Layer 의 전체 소스는 아래 첨부하였습니다. :)

VectorLayers.html
0.01MB

안되시는 분이나 궁금하신 분들은 언제든 질문 남겨주시면 성실히 답변하겠습니다!!

기존에는 매우 쉽게 생각해서 이 글에 Vector 와 Image Layer를 모두 올려볼 생각이였는데, 생각보다 길어지는 관계로 

2편에서는 Image Layer 에 대하여 설명해보도록 하겠습니다!

 

감사합니다 :)

'GIS > Openlayers' 카테고리의 다른 글

[Openlayers] Vector Layer 와 Image Layer - 2  (0) 2022.01.12
[Openlayers] GIS 오픈소스 Openlayers란?  (2) 2022.01.06

안녕하세요. 씨엔스 입니다.

오늘은 GIS 개발에서 오픈소스로 사용되고 있는 Openlayers 에 대하여 간략하게 소개하고 설명해보려 합니다.

지도서비스를 개발하기 위해서 Kakao나 Naver의 지도 API를 사용하는 경우도 있지만 오픈소스로 이용할 수 있는 라이브러리가 바로 Openlayers 인데요.

PostgreSQL 과 Geoserver 를 이용하여 매우 다양하게 사용할 수 있는 장점이 있습니다.

 

www.openlayers.org

 

OpenLayers - Welcome

A high-performance, feature-packed library for all your mapping needs.

openlayers.org

 

 

GIS 에 대해 자세히 설명하기 전에 기본 지식이 있다는 가정하에 글을 작성합니다.

우선 Openlayers 의 구조를 간단하게 설명해보도록 하겠습니다.

설명이 잘못되었거나 부족하다면 언제든 댓글 남겨주세요! 더 열심히 공부하겠습니다.

Openlayers 구조

 

Openlayers에서 지도서비스가 나타나기 위해서는 Map이 핵심요소로 필요합니다.

Map은 지도화면에 배경지도(BingMaps, GoogleMap, Kakao, Naver, OSM, 브이월드) 또는 배경지도 위에 WMS/WFS 레이어를 표출하기 위해 필요한 필수 객체 입니다.

Map을 사용하여 지도를 화면에 표출하기 위해서는 Layer와 View, Interaction, Controls 와 같은 요소가 필요한데 각각을 설명하자면,

Layer는 배경지도 또는 레이어를 표출하기 위해 사용하는 객체입니다. 하나 이상의 레이어가 필요하여 Map에서 배열로 사용됩니다.

View는 해상도, 화면 레벨, 좌표 등 지도의 시각적 효과에 대한 변수들을 정의합니다.

Controls는 화면에 고정된 위치에 사용되는 버튼등에 이벤트를 적용하여 회전, 위치이동 등의 이벤트를 제공합니다.

Interaction은 마우스, 키보드 등의 이벤트로 효과를 제공합니다. (true, false)

 

설명만으로는 이해가 잘 안될 수 있어 Openlayer를 이용하여 지도 화면을 띄워보도록 하겠습니다.

 

1
2
3
<link rel="stylesheet" href="https://openlayers.org/en/v4.6.5/css/ol.css" type="text/css">
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<script src="https://openlayers.org/en/v4.6.5/build/ol.js"></script>
cs

먼저 Openlayers를 사용하기 위해 js 와 css를 추가해줍니다.

 

1
<div id="map" class="map" style="width: 600px; height: 50%;"></div>
cs

그리고 body 안에 div 태그에서 지도 화면을 띄울 곳을 정합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
    var layers = [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        })
    ];
    var map = new ol.Map({
        layers: layers,
        target: 'map',
        view: new ol.View({
          center: [14206608.0228070654345508.083662013],
          zoom: 6
        })
    });
</script>
cs

Map 객체에 필요한 Layer와 View 객체를 추가하여 OSM기반의 배경지도를 화면에 표출하는 소스 입니다.

 

이렇게 하여 전체 소스를 정리하자면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Openlayers-baseMap</title>
    <link rel="stylesheet" href="https://openlayers.org/en/v4.6.5/css/ol.css" type="text/css">
    <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
    <script src="https://openlayers.org/en/v4.6.5/build/ol.js"></script>
</head>
<body>
    <div id="map" class="map" style="width: 600px; height: 50%;"></div>
    <script>
      var layers = [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        })
      ];
      var map = new ol.Map({
        layers: layers,
        target: 'map',
        view: new ol.View({
          center: [14206608.0228070654345508.083662013],
          zoom: 6
        })
      });
    </script>
</body>
</html>
cs

이런 식으로 HTML 소스를 작성하게 됩니다.

 

HTML파일을 저장하고 브라우저로 열면 화면이 나타나게 됩니다.

 

Map, Layer, View 객체를 이용하여 가장 간단하고 따라하기 쉬운 지도화면을 만들어 보았습니다.

Openlayers의 장점은 이처럼 간단하고 누구나 따라하여 사용할 수 있다는거같아요!

 

여기에 추가로 WMS나 WFS, Vector, Tile 등 다양한 Layer 객체를 배경지도 위에 올려 표현할 수 있고,

PostgreSQL등의 공간정보 사용이 가능한 데이터베이스와 Geoserver를 이용하여 많은 퍼포먼스를 낼 수 있는 

Openlayers의 기능을 적어보려 합니다.

 

감사합니다!

'GIS > Openlayers' 카테고리의 다른 글

[Openlayers] Vector Layer 와 Image Layer - 2  (0) 2022.01.12
[Openlayers] Vector Layer 와 Image Layer - 1  (0) 2022.01.07

+ Recent posts